[cairo-commit] 3 commits - src/cairo.c src/cairo-clip.c src/cairo-clip-private.h src/cairo-gstate.c src/cairo.h src/cairoint.h src/cairo-matrix.c test/get-clip.c test/get-path-extents.c test/Makefile.am test/Makefile.win32

Vladimir Vukicevic vladimir at kemper.freedesktop.org
Mon Sep 25 23:24:19 PDT 2006


 src/cairo-clip-private.h |    5 
 src/cairo-clip.c         |  127 +++++++++++++++++++++
 src/cairo-gstate.c       |  109 +++++++++++++-----
 src/cairo-matrix.c       |   18 ++-
 src/cairo.c              |   89 +++++++++++++++
 src/cairo.h              |   36 +++++-
 src/cairoint.h           |   19 +++
 test/Makefile.am         |    2 
 test/Makefile.win32      |    2 
 test/get-clip.c          |  277 +++++++++++++++++++++++++++++++++++++++++++++++
 test/get-path-extents.c  |  199 +++++++++++++++++++++++++++++++++
 11 files changed, 852 insertions(+), 31 deletions(-)

New commits:
diff-tree 191e108b93ef6d39832e78323a18cc4c795c7ca3 (from a8ca155f83098c02fb8d3acc57b0492d5b753d54)
Author: Robert O'Callahan <robert at ocallahan.org>
Date:   Mon Sep 25 23:22:45 2006 -0700

    Add clip getters API + tests
    
    Add new public API methods:
    
    void cairo_clip_extents (cairo_t *cr,                                               double *x1, double *y1,
        double *x2, double *y2);
    cairo_rectangle_list_t *cairo_copy_clip_rectangles (cairo_t *);
    void cairo_rectangle_list_destroy (cairo_rectangle_list_t *);
    
    Also add 'get-clip' and 'get-path-extents' tests.

diff --git a/src/cairo-clip-private.h b/src/cairo-clip-private.h
index b1a243e..d3255fa 100644
--- a/src/cairo-clip-private.h
+++ b/src/cairo-clip-private.h
@@ -38,6 +38,8 @@
 
 #include "cairo-path-fixed-private.h"
 
+extern cairo_private const cairo_rectangle_list_t _cairo_rectangles_nil;
+
 struct _cairo_clip_path {
     unsigned int	ref_count;
     cairo_path_fixed_t	path;
@@ -124,4 +126,7 @@ _cairo_clip_translate (cairo_clip_t  *cl
                        cairo_fixed_t  tx,
                        cairo_fixed_t  ty);
 
+cairo_private cairo_rectangle_list_t*
+_cairo_clip_copy_rectangles (cairo_clip_t *clip, cairo_gstate_t *gstate);
+
 #endif /* CAIRO_CLIP_PRIVATE_H */
diff --git a/src/cairo-clip.c b/src/cairo-clip.c
index 630ec89..a0f67fa 100644
--- a/src/cairo-clip.c
+++ b/src/cairo-clip.c
@@ -118,6 +118,39 @@ _cairo_clip_reset (cairo_clip_t *clip)
     return CAIRO_STATUS_SUCCESS;
 }
 
+static cairo_status_t
+_cairo_clip_path_intersect_to_rectangle (cairo_clip_path_t       *clip_path,
+   				         cairo_rectangle_int16_t *rectangle)
+{
+    while (clip_path) {
+        cairo_status_t status;
+        cairo_traps_t traps;
+        cairo_box_t extents;
+        cairo_rectangle_int16_t extents_rect;
+
+        _cairo_traps_init (&traps);
+
+        status = _cairo_path_fixed_fill_to_traps (&clip_path->path,
+                                                  clip_path->fill_rule,
+                                                  clip_path->tolerance,
+                                                  &traps);
+        if (status) {
+            _cairo_traps_fini (&traps);
+            return status;
+        }
+
+        _cairo_traps_extents (&traps, &extents);
+        _cairo_box_round_to_rectangle (&extents, &extents_rect);
+        _cairo_rectangle_intersect (rectangle, &extents_rect);
+
+        _cairo_traps_fini (&traps);
+
+        clip_path = clip_path->prev;
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
 cairo_status_t
 _cairo_clip_intersect_to_rectangle (cairo_clip_t            *clip,
 				    cairo_rectangle_int16_t *rectangle)
@@ -126,7 +159,12 @@ _cairo_clip_intersect_to_rectangle (cair
 	return CAIRO_STATUS_SUCCESS;
 
     if (clip->path) {
-	/* Intersect path extents here. */
+        cairo_status_t status;
+        
+        status = _cairo_clip_path_intersect_to_rectangle (clip->path,
+                                                          rectangle);
+        if (status)
+            return status;
     }
 
     if (clip->region) {
@@ -534,3 +572,90 @@ _cairo_clip_init_deep_copy (cairo_clip_t
         }
     }
 }
+
+const cairo_rectangle_list_t _cairo_rectangles_nil =
+  { CAIRO_STATUS_NO_MEMORY, NULL, 0 };
+static const cairo_rectangle_list_t _cairo_rectangles_not_representable =
+  { CAIRO_STATUS_CLIP_NOT_REPRESENTABLE, NULL, 0 };
+
+static cairo_bool_t
+_cairo_clip_rect_to_user (cairo_gstate_t *gstate,
+                          double x, double y, double width, double height,
+                          cairo_rectangle_t *rectangle)
+{
+    double x2 = x + width;
+    double y2 = y + height;
+    cairo_bool_t is_tight;
+
+    _cairo_gstate_backend_to_user_rectangle (gstate, &x, &y, &x2, &y2, &is_tight);
+    rectangle->x = x;
+    rectangle->y = y;
+    rectangle->width = x2 - x;
+    rectangle->height = y2 - y;
+    return is_tight;
+}
+
+cairo_private cairo_rectangle_list_t*
+_cairo_clip_copy_rectangles (cairo_clip_t *clip, cairo_gstate_t *gstate)
+{
+    cairo_rectangle_list_t *list;
+    cairo_rectangle_t *rectangles;
+    int n_boxes;
+
+    if (clip->path || clip->surface)
+        return (cairo_rectangle_list_t*) &_cairo_rectangles_not_representable;
+
+    n_boxes = clip->region ? pixman_region_num_rects (clip->region) : 1;
+    rectangles = malloc (sizeof (cairo_rectangle_t)*n_boxes);
+    if (rectangles == NULL)
+        return (cairo_rectangle_list_t*) &_cairo_rectangles_nil;
+
+    if (clip->region) {
+        pixman_box16_t *boxes;
+        int i;
+        
+        boxes = pixman_region_rects (clip->region);
+        for (i = 0; i < n_boxes; ++i) {
+            if (!_cairo_clip_rect_to_user(gstate, boxes[i].x1, boxes[i].y1,
+                                          boxes[i].x2 - boxes[i].x1,
+                                          boxes[i].y2 - boxes[i].y1,
+                                          &rectangles[i])) {
+                free (rectangles);
+                return (cairo_rectangle_list_t*)
+                    &_cairo_rectangles_not_representable;
+            }
+        }
+    } else {
+        cairo_rectangle_int16_t extents;
+        _cairo_surface_get_extents (_cairo_gstate_get_target (gstate), &extents);
+        if (!_cairo_clip_rect_to_user(gstate, extents.x, extents.y,
+                                      extents.width, extents.height,
+                                      rectangles)) {
+            free (rectangles);
+            return (cairo_rectangle_list_t*)
+                &_cairo_rectangles_not_representable;
+        }
+    }
+
+    list = malloc (sizeof (cairo_rectangle_list_t));
+    if (list == NULL) {
+        free (rectangles);
+        return (cairo_rectangle_list_t*) &_cairo_rectangles_nil;
+    }
+    
+    list->status = CAIRO_STATUS_SUCCESS;
+    list->rectangles = rectangles;
+    list->num_rectangles = n_boxes;
+    return list;
+}
+
+void
+cairo_rectangle_list_destroy (cairo_rectangle_list_t *rectangle_list)
+{
+    if (rectangle_list == NULL || rectangle_list == &_cairo_rectangles_nil ||
+        rectangle_list == &_cairo_rectangles_not_representable)
+        return;
+
+    free (rectangle_list->rectangles);
+    free (rectangle_list);
+}
diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c
index 7fee690..8beb8c4 100644
--- a/src/cairo-gstate.c
+++ b/src/cairo-gstate.c
@@ -1148,6 +1148,40 @@ _cairo_gstate_clip (cairo_gstate_t *gsta
 			     gstate->antialias, gstate->target);
 }
 
+cairo_status_t
+_cairo_gstate_clip_extents (cairo_gstate_t *gstate,
+		            double         *x1,
+		            double         *y1,
+        		    double         *x2,
+        		    double         *y2)
+{
+    cairo_rectangle_int16_t extents;
+    cairo_status_t status;
+    
+    status = _cairo_surface_get_extents (gstate->target, &extents);
+    if (status)
+        return status;
+
+    status = _cairo_clip_intersect_to_rectangle (&gstate->clip, &extents);
+    if (status)
+        return status;
+    
+    *x1 = extents.x;
+    *y1 = extents.y;
+    *x2 = extents.x + extents.width;
+    *y2 = extents.y + extents.height;
+
+    _cairo_gstate_backend_to_user_rectangle (gstate, x1, y1, x2, y2, NULL);
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+cairo_rectangle_list_t*
+_cairo_gstate_copy_clip_rectangles (cairo_gstate_t *gstate)
+{
+    return _cairo_clip_copy_rectangles (&gstate->clip, gstate);
+}
+
 static void
 _cairo_gstate_unset_scaled_font (cairo_gstate_t *gstate)
 {
diff --git a/src/cairo.c b/src/cairo.c
index 11a3190..1a7da53 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -2209,6 +2209,67 @@ cairo_reset_clip (cairo_t *cr)
 }
 
 /**
+ * cairo_clip_extents:
+ * @cr: a cairo context
+ * @x1: left of the resulting extents
+ * @y1: top of the resulting extents
+ * @x2: right of the resulting extents
+ * @y2: bottom of the resulting extents
+ *
+ * Computes a bounding box in user coordinates covering the area inside the
+ * current clip.
+ **/
+void
+cairo_clip_extents (cairo_t *cr,
+		    double *x1, double *y1,
+		    double *x2, double *y2)
+{
+    if (cr->status)
+	return;
+
+    cr->status = _cairo_gstate_clip_extents (cr->gstate, x1, y1, x2, y2);
+    if (cr->status)
+	_cairo_set_error (cr, cr->status);
+}
+
+static cairo_rectangle_list_t *
+_cairo_rectangle_list_create_for_status (cairo_status_t status)
+{
+    cairo_rectangle_list_t *list;
+
+    list = malloc (sizeof (cairo_rectangle_list_t));
+    if (list == NULL)
+        return (cairo_rectangle_list_t*) &_cairo_rectangles_nil;
+    list->status = status;
+    list->rectangles = NULL;
+    list->num_rectangles = 0;
+    return list;
+}
+
+/**
+ * cairo_copy_clip_rectangles:
+ *
+ * Returns the current clip region as a list of rectangles in user coordinates.
+ * Never returns %NULL.
+ *
+ * The status in the list may be CAIRO_STATUS_CLIP_NOT_REPRESENTABLE to
+ * indicate that the clip region cannot be represented as a list of
+ * user-space rectangles. The status may have other values to indicate
+ * other errors.
+ *
+ * The caller must always call cairo_rectangle_list_destroy on the result of
+ * this function.
+ **/
+cairo_rectangle_list_t *
+cairo_copy_clip_rectangles (cairo_t *cr)
+{
+    if (cr->status)
+        return _cairo_rectangle_list_create_for_status (cr->status);
+
+    return _cairo_gstate_copy_clip_rectangles (cr->gstate);
+}
+
+/**
  * cairo_select_font_face:
  * @cr: a #cairo_t
  * @family: a font family name, encoded in UTF-8
@@ -3097,6 +3158,8 @@ cairo_status_to_string (cairo_status_t s
 	return "invalid value for a DSC comment";
     case CAIRO_STATUS_INVALID_INDEX:
 	return "invalid index passed to getter";
+    case CAIRO_STATUS_CLIP_NOT_REPRESENTABLE:
+        return "clip region not representable in desired format";
     }
 
     return "<unknown error status>";
diff --git a/src/cairo.h b/src/cairo.h
index a0c2f54..7a3b653 100644
--- a/src/cairo.h
+++ b/src/cairo.h
@@ -170,6 +170,7 @@ typedef struct _cairo_user_data_key {
  * @CAIRO_STATUS_INVALID_DASH: invalid value for a dash setting
  * @CAIRO_STATUS_INVALID_DSC_COMMENT: invalid value for a DSC comment (Since 1.2)
  * @CAIRO_STATUS_INVALID_INDEX: invalid index passed to getter
+ * @CAIRO_STATUS_CLIP_NOT_REPRESENTABLE: clip region not representable in desired format (Since 1.4)
  *
  * #cairo_status_t is used to indicate errors that can occur when
  * using Cairo. In some cases it is returned directly by functions.
@@ -201,7 +202,8 @@ typedef enum _cairo_status {
     CAIRO_STATUS_FILE_NOT_FOUND,
     CAIRO_STATUS_INVALID_DASH,
     CAIRO_STATUS_INVALID_DSC_COMMENT,
-    CAIRO_STATUS_INVALID_INDEX
+    CAIRO_STATUS_INVALID_INDEX,
+    CAIRO_STATUS_CLIP_NOT_REPRESENTABLE
 } cairo_status_t;
 
 /**
@@ -589,6 +591,38 @@ cairo_clip (cairo_t *cr);
 cairo_public void
 cairo_clip_preserve (cairo_t *cr);
 
+cairo_public void
+cairo_clip_extents (cairo_t *cr,
+		    double *x1, double *y1,
+		    double *x2, double *y2);
+
+/**
+ * cairo_rectangle_t:
+ * 
+ * A data structure for holding a rectangle.
+ */
+typedef struct _cairo_rectangle {
+    double x, y, width, height;
+} cairo_rectangle_t;
+
+/**
+ * cairo_rectangle_list_t:
+ * 
+ * A data structure for holding a dynamically allocated
+ * array of rectangles.
+ */
+typedef struct _cairo_rectangle_list {
+    cairo_status_t     status;
+    cairo_rectangle_t *rectangles;
+    int                num_rectangles;
+} cairo_rectangle_list_t;
+
+cairo_public cairo_rectangle_list_t *
+cairo_copy_clip_rectangles (cairo_t *cr);
+
+cairo_public void
+cairo_rectangle_list_destroy (cairo_rectangle_list_t *rectangle_list);
+
 /* Font/Text functions */
 
 /**
diff --git a/src/cairoint.h b/src/cairoint.h
index 23a7750..6a3bc31 100755
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1339,6 +1339,16 @@ cairo_private cairo_status_t
 _cairo_gstate_reset_clip (cairo_gstate_t *gstate);
 
 cairo_private cairo_status_t
+_cairo_gstate_clip_extents (cairo_gstate_t *gstate,
+		            double         *x1,
+		            double         *y1,
+        		    double         *x2,
+        		    double         *y2);
+
+cairo_private cairo_rectangle_list_t*
+_cairo_gstate_copy_clip_rectangles (cairo_gstate_t *gstate);
+
+cairo_private cairo_status_t
 _cairo_gstate_show_surface (cairo_gstate_t	*gstate,
 			    cairo_surface_t	*surface,
 			    double		 x,
diff --git a/test/Makefile.am b/test/Makefile.am
index d1706ac..763203b 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -38,7 +38,9 @@ font-face-get-type		\
 font-matrix-translation		\
 glyph-cache-pressure		\
 get-and-set			\
+get-clip                        \
 get-group-target		\
+get-path-extents                \
 gradient-alpha			\
 leaky-dash			\
 leaky-polygon			\
diff --git a/test/Makefile.win32 b/test/Makefile.win32
index e364de8..c2e7338 100644
--- a/test/Makefile.win32
+++ b/test/Makefile.win32
@@ -37,7 +37,9 @@ font-face-get-type		\
 font-matrix-translation		\
 glyph-cache-pressure		\
 get-and-set			\
+get-clip			\
 get-group-target		\
+get-path-extents		\
 gradient-alpha			\
 leaky-dash			\
 leaky-polygon			\
diff --git a/test/get-clip.c b/test/get-clip.c
new file mode 100644
index 0000000..cfd8141
--- /dev/null
+++ b/test/get-clip.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright © 2006 Novell, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * Novell, Inc. not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Novell, Inc. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Robert O'Callahan <rocallahan at novell.com>
+ */
+
+#include "cairo-test.h"
+#include <stddef.h>
+
+static cairo_test_draw_function_t draw;
+
+cairo_test_t test = {
+    "get-clip",
+    "Test cairo_copy_clip_rectangles and cairo_clip_extents",
+    0, 0,
+    draw
+};
+
+static cairo_bool_t
+check_count (const char *message, cairo_bool_t uses_clip_rects,
+             cairo_rectangle_list_t *list, int expected)
+{
+    if (!uses_clip_rects) {
+        if (expected == 0 && list->num_rectangles == 0)
+            return 1;
+        cairo_test_log ("Error: %s; cairo_copy_clip_rectangles unexpectedly got %d rectangles\n",
+                        message, list->num_rectangles);
+        return 0;
+    }
+
+    if (list->status != CAIRO_STATUS_SUCCESS) {
+        cairo_test_log ("Error: %s; cairo_copy_clip_rectangles failed with \"%s\"\n",
+                        message, cairo_status_to_string(list->status));
+        return 0;
+    }
+
+    if (list->num_rectangles == expected)
+        return 1;
+    cairo_test_log ("Error: %s; expected %d rectangles, got %d\n", message,
+                    expected, list->num_rectangles);
+    return 0;
+}
+
+static cairo_bool_t
+check_unrepresentable (const char *message, cairo_rectangle_list_t *list)
+{
+    if (list->status != CAIRO_STATUS_CLIP_NOT_REPRESENTABLE) {
+        cairo_test_log ("Error: %s; cairo_copy_clip_rectangles got unexpected result \"%s\"\n"
+                        " (we expected CAIRO_STATUS_CLIP_NOT_REPRESENTABLE)",
+                        message, cairo_status_to_string(list->status));
+        return 0;
+    }
+    return 1;
+}
+
+static cairo_bool_t
+check_rectangles_contain (const char *message, cairo_bool_t uses_clip_rects,
+                          cairo_rectangle_list_t *list,
+                          double x, double y, double width, double height)
+{
+    int i;
+
+    if (!uses_clip_rects)
+        return 1;
+
+    for (i = 0; i < list->num_rectangles; ++i) {
+        if (list->rectangles[i].x == x && list->rectangles[i].y == y &&
+            list->rectangles[i].width == width && list->rectangles[i].height == height)
+            return 1;
+    }
+    cairo_test_log ("Error: %s; rectangle list does not contain rectangle %f,%f,%f,%f\n",
+                    message, x, y, width, height);
+    return 0;
+}
+
+static cairo_bool_t
+check_clip_extents (const char *message, cairo_t *cr,
+                    double x, double y, double width, double height)
+{
+    double ext_x1, ext_y1, ext_x2, ext_y2;
+    cairo_clip_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
+    if (ext_x1 == x && ext_y1 == y && ext_x2 == x + width && ext_y2 == y + height)
+        return 1;
+    cairo_test_log ("Error: %s; clip extents %f,%f,%f,%f should be %f,%f,%f,%f\n",
+                    message, ext_x1, ext_y1, ext_x2 - ext_x1, ext_y2 - ext_y1,
+                    x, y, width, height);
+    return 0;
+}
+
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height)
+{
+    cairo_surface_t        *surface;
+    cairo_t                *cr2;
+    cairo_rectangle_list_t *rectangle_list;
+    const char             *phase;
+    cairo_bool_t            uses_clip_rects;
+    
+    surface = cairo_surface_create_similar (cairo_get_target (cr),
+                                            CAIRO_CONTENT_COLOR, 100, 100);
+    /* don't use cr accidentally */
+    cr = NULL;
+    cr2 = cairo_create (surface);
+    cairo_surface_destroy (surface);
+
+    /* check the surface type so we ignore cairo_copy_clip_rectangles failures
+       on surface types that don't use rectangle lists for clipping */
+    switch (cairo_surface_get_type (surface)) {
+    case CAIRO_SURFACE_TYPE_PDF:
+    case CAIRO_SURFACE_TYPE_PS:
+    case CAIRO_SURFACE_TYPE_SVG:
+        uses_clip_rects = 0;
+        break;
+    case CAIRO_SURFACE_TYPE_IMAGE:
+    case CAIRO_SURFACE_TYPE_XLIB:
+    case CAIRO_SURFACE_TYPE_XCB:
+    case CAIRO_SURFACE_TYPE_GLITZ:
+    case CAIRO_SURFACE_TYPE_QUARTZ:
+    case CAIRO_SURFACE_TYPE_WIN32:
+    case CAIRO_SURFACE_TYPE_BEOS:
+    case CAIRO_SURFACE_TYPE_DIRECTFB:
+    default:
+        uses_clip_rects = 1;
+        break;
+    }
+
+    /* first, test basic stuff. This should not be clipped, it should
+       return the surface rectangle. */
+    phase = "No clip set";
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+    if (!check_count (phase, uses_clip_rects, rectangle_list, 1) ||
+        !check_clip_extents (phase, cr2, 0, 0, 100, 100) ||
+        !check_rectangles_contain(phase, uses_clip_rects, rectangle_list, 0, 0, 100, 100)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+
+    /* Test simple clip rect. */
+    phase = "Simple clip rect";
+    cairo_save (cr2);
+    cairo_rectangle (cr2, 10, 10, 80, 80);
+    cairo_clip (cr2);
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+    if (!check_count (phase, uses_clip_rects, rectangle_list, 1) ||
+        !check_clip_extents (phase, cr2, 10, 10, 80, 80) ||
+        !check_rectangles_contain(phase, uses_clip_rects, rectangle_list, 10, 10, 80, 80)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+    cairo_restore (cr2);
+
+    /* Test everything clipped out. */
+    phase = "All clipped out";
+    cairo_save (cr2);
+    cairo_clip (cr2);
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+    if (!check_count (phase, uses_clip_rects, rectangle_list, 0)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+    cairo_restore (cr2);
+    
+    /* test two clip rects */
+    phase = "Two clip rects";
+    cairo_save (cr2);
+    cairo_rectangle (cr2, 10, 10, 10, 10);
+    cairo_rectangle (cr2, 20, 20, 10, 10);
+    cairo_clip (cr2);
+    cairo_rectangle (cr2, 15, 15, 10, 10);
+    cairo_clip (cr2);
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+    if (!check_count (phase, uses_clip_rects, rectangle_list, 2) ||
+        !check_clip_extents (phase, cr2, 15, 15, 10, 10) ||
+        !check_rectangles_contain(phase, uses_clip_rects, rectangle_list, 15, 15, 5, 5) ||
+        !check_rectangles_contain(phase, uses_clip_rects, rectangle_list, 20, 20, 5, 5)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+    cairo_restore (cr2);
+
+    /* test non-rectangular clip */
+    phase = "Nonrectangular clip";
+    cairo_save (cr2);
+    cairo_move_to (cr2, 0, 0);
+    cairo_line_to (cr2, 100, 100);
+    cairo_line_to (cr2, 100, 0);
+    cairo_close_path (cr2);
+    cairo_clip (cr2);
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+     /* can't get this in one tight user-space rectangle */
+    if (!check_unrepresentable (phase, rectangle_list) ||
+        !check_clip_extents (phase, cr2, 0, 0, 100, 100)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+    cairo_restore (cr2);
+    
+    phase = "User space, simple scale, getting clip with same transform";
+    cairo_save (cr2);
+    cairo_scale (cr2, 2, 2);
+    cairo_rectangle (cr2, 5, 5, 40, 40);
+    cairo_clip (cr2);
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+    if (!check_count (phase, uses_clip_rects, rectangle_list, 1) ||
+        !check_clip_extents (phase, cr2, 5, 5, 40, 40) ||
+        !check_rectangles_contain(phase, uses_clip_rects, rectangle_list, 5, 5, 40, 40)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+    cairo_restore (cr2);
+
+    phase = "User space, simple scale, getting clip with no transform";
+    cairo_save (cr2);
+    cairo_save (cr2);
+    cairo_scale (cr2, 2, 2);
+    cairo_rectangle (cr2, 5, 5, 40, 40);
+    cairo_restore (cr2);
+    cairo_clip (cr2);
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+    if (!check_count (phase, uses_clip_rects, rectangle_list, 1) ||
+        !check_clip_extents (phase, cr2, 10, 10, 80, 80) ||
+        !check_rectangles_contain(phase, uses_clip_rects, rectangle_list, 10, 10, 80, 80)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+    cairo_restore (cr2);
+
+    phase = "User space, rotation, getting clip with no transform";
+    cairo_save (cr2);
+    cairo_save (cr2);
+    cairo_rotate (cr2, 12);
+    cairo_rectangle (cr2, 5, 5, 40, 40);
+    cairo_restore (cr2);
+    cairo_clip (cr2);
+    rectangle_list = cairo_copy_clip_rectangles (cr2);
+    if (!check_unrepresentable (phase, rectangle_list)) {
+        cairo_rectangle_list_destroy (rectangle_list);
+	return CAIRO_TEST_FAILURE;
+    }
+    cairo_rectangle_list_destroy (rectangle_list);
+    cairo_restore (cr2);
+
+    cairo_destroy (cr2);
+    return CAIRO_TEST_SUCCESS;
+}
+
+int
+main (void)
+{
+    return cairo_test (&test);
+}
diff --git a/test/get-path-extents.c b/test/get-path-extents.c
new file mode 100644
index 0000000..e755f84
--- /dev/null
+++ b/test/get-path-extents.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright © 2006 Novell, Inc.
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * Novell, Inc. not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Novell, Inc. makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
+ * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Author: Robert O'Callahan <rocallahan at novell.com>
+ */
+
+#include "cairo-test.h"
+#include <stddef.h>
+#include <math.h>
+
+static cairo_test_draw_function_t draw;
+
+cairo_test_t test = {
+    "get-path-extents",
+    "Test cairo_fill_extents and cairo_stroke_extents",
+    0, 0,
+    draw
+};
+
+enum ExtentsType { FILL, STROKE };
+
+enum Relation { EQUALS, CONTAINS };
+
+static cairo_bool_t
+check_extents (const char *message, cairo_t *cr, enum ExtentsType type,
+               enum Relation relation,
+               double x, double y, double width, double height)
+{
+    double ext_x1, ext_y1, ext_x2, ext_y2;
+    const char *type_string;
+    const char *relation_string;
+
+    switch (type) {
+    default:
+    case FILL:
+        type_string = "fill";
+        cairo_fill_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
+        break;
+    case STROKE:
+        type_string = "stroke";
+        cairo_stroke_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
+        break;
+    }
+    
+    /* let empty rects match */
+    if ((ext_x1 == ext_x2 || ext_y1 == ext_y2) && (width == 0 || height == 0))
+        return 1;
+
+    switch (relation) {
+    default:
+    case EQUALS:
+        relation_string = "equal";
+        if (ext_x1 == x && ext_y1 == y && ext_x2 == x + width && ext_y2 == y + height)
+            return 1;
+        break;
+    case CONTAINS:
+        relation_string = "contain";
+        if (width == 0 || height == 0) {
+            /* odd test that doesn't really test anything... */
+            return 1;
+        }
+        if (ext_x1 <= x && ext_y1 <= y && ext_x2 >= x + width && ext_y2 >= y + height)
+            return 1;
+        break;
+    }
+
+    cairo_test_log ("Error: %s; %s extents %f,%f,%f,%f should %s %f,%f,%f,%f\n",
+                    message, type_string,
+                    ext_x1, ext_y1, ext_x2 - ext_x1, ext_y2 - ext_y1,
+                    relation_string,
+                    x, y, width, height);
+    return 0;
+}
+
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height)
+{
+    cairo_surface_t *surface;
+    cairo_t         *cr2;
+    const char      *phase;
+    
+    surface = cairo_surface_create_similar (cairo_get_target (cr),
+                                            CAIRO_CONTENT_COLOR, 100, 100);
+    /* don't use cr accidentally */
+    cr = NULL;
+    cr2 = cairo_create (surface);
+    cairo_surface_destroy (surface);
+
+    cairo_set_line_width (cr2, 10);
+    cairo_set_line_join (cr2, CAIRO_LINE_JOIN_MITER);
+    cairo_set_miter_limit (cr2, 100);
+
+    phase = "No path";
+    if (!check_extents (phase, cr2, FILL, EQUALS, 0, 0, 0, 0) ||
+        !check_extents (phase, cr2, STROKE, EQUALS, 0, 0, 0, 0))
+	return CAIRO_TEST_FAILURE;
+
+    phase = "Simple rect";
+    cairo_save (cr2);
+    cairo_rectangle (cr2, 10, 10, 80, 80);
+    if (!check_extents (phase, cr2, FILL, EQUALS, 10, 10, 80, 80) ||
+        !check_extents (phase, cr2, STROKE, EQUALS, 5, 5, 90, 90))
+	return CAIRO_TEST_FAILURE;
+    cairo_new_path (cr2);
+    cairo_restore (cr2);
+
+    phase = "Two rects";
+    cairo_save (cr2);
+    cairo_rectangle (cr2, 10, 10, 10, 10);
+    cairo_rectangle (cr2, 20, 20, 10, 10);
+    if (!check_extents (phase, cr2, FILL, EQUALS, 10, 10, 20, 20) ||
+        !check_extents (phase, cr2, STROKE, EQUALS, 5, 5, 30, 30))
+	return CAIRO_TEST_FAILURE;
+    cairo_new_path (cr2);
+    cairo_restore (cr2);
+
+    phase = "Triangle";
+    cairo_save (cr2);
+    cairo_move_to (cr2, 10, 10);
+    cairo_line_to (cr2, 90, 90);
+    cairo_line_to (cr2, 90, 10);
+    cairo_close_path (cr2);
+    /* miter joins protrude 5*(1+sqrt(2)) above the top-left corner and to
+       the right of the bottom-right corner */
+    if (!check_extents (phase, cr2, FILL, EQUALS, 10, 10, 80, 80) ||
+        !check_extents (phase, cr2, STROKE, CONTAINS, 0, 5, 95, 95))
+	return CAIRO_TEST_FAILURE;
+    cairo_new_path (cr2);
+    cairo_restore (cr2);
+    
+    phase = "User space, simple scale, getting extents with same transform";
+    cairo_save (cr2);
+    cairo_scale (cr2, 2, 2);
+    cairo_rectangle (cr2, 5, 5, 40, 40);
+    if (!check_extents (phase, cr2, FILL, EQUALS, 5, 5, 40, 40) ||
+        !check_extents (phase, cr2, STROKE, EQUALS, 0, 0, 50, 50))
+	return CAIRO_TEST_FAILURE;
+    cairo_new_path (cr2);
+    cairo_restore (cr2);
+
+    phase = "User space, simple scale, getting extents with no transform";
+    cairo_save (cr2);
+    cairo_save (cr2);
+    cairo_scale (cr2, 2, 2);
+    cairo_rectangle (cr2, 5, 5, 40, 40);
+    cairo_restore (cr2);
+    if (!check_extents (phase, cr2, FILL, EQUALS, 10, 10, 80, 80) ||
+        !check_extents (phase, cr2, STROKE, EQUALS, 5, 5, 90, 90))
+	return CAIRO_TEST_FAILURE;
+    cairo_new_path (cr2);
+    cairo_restore (cr2);
+
+    phase = "User space, rotation, getting extents with transform";
+    cairo_save (cr2);
+    cairo_rectangle (cr2, -50, -50, 50, 50);
+    cairo_rotate (cr2, -M_PI/4);
+    /* the path in user space is now (nearly) the square rotated by
+       45 degrees about the origin. Thus its x1 and x2 are both nearly 0.
+       This should show any bugs where we just transform device-space
+       x1,y1 and x2,y2 to get the extents. */
+    /* The largest axis-aligned square inside the rotated path has
+       side lengths 50*sqrt(2), so a bit over 35 on either side of
+       the axes. With the stroke width added to the rotated path,
+       the largest axis-aligned square is a bit over 38 on either side of
+       the axes. */
+    if (!check_extents (phase, cr2, FILL, CONTAINS, -35, -35, 35, 35) ||
+        !check_extents (phase, cr2, STROKE, CONTAINS, -38, -38, 38, 38))
+	return CAIRO_TEST_FAILURE;
+    cairo_new_path (cr2);
+    cairo_restore (cr2);
+
+    cairo_destroy (cr2);
+    return CAIRO_TEST_SUCCESS;
+}
+
+int
+main (void)
+{
+    return cairo_test (&test);
+}
diff-tree a8ca155f83098c02fb8d3acc57b0492d5b753d54 (from 37fa632e59b7325041f689bf1a56e08d04379c96)
Author: Robert O'Callahan <robert at ocallahan.org>
Date:   Mon Sep 25 23:16:54 2006 -0700

    Fix stroke/fill extents bounding boxes
    
    Correctly return the transformed bounding box for stroke/fill extents,
    instead of just transforming the two corners separately.

diff --git a/src/cairo-gstate.c b/src/cairo-gstate.c
index 832c520..7fee690 100644
--- a/src/cairo-gstate.c
+++ b/src/cairo-gstate.c
@@ -706,6 +706,25 @@ _cairo_gstate_backend_to_user (cairo_gst
     cairo_matrix_transform_point (&gstate->ctm_inverse, x, y);
 }
 
+void
+_cairo_gstate_backend_to_user_rectangle (cairo_gstate_t *gstate,
+                                         double *x1, double *y1,
+                                         double *x2, double *y2,
+                                         cairo_bool_t *is_tight)
+{
+    double width = *x2 - *x1;
+    double height = *y2 - *y1;
+    cairo_matrix_t matrix_inverse;
+
+    cairo_matrix_multiply (&matrix_inverse, &gstate->ctm_inverse,
+                           &gstate->target->device_transform_inverse);
+    _cairo_matrix_transform_bounding_box (
+        &matrix_inverse, x1, y1, &width, &height, is_tight);
+
+    *x2 = *x1 + width;
+    *y2 = *y1 + height;
+}
+
 /* XXX: NYI
 cairo_status_t
 _cairo_gstate_stroke_to_path (cairo_gstate_t *gstate)
@@ -1041,6 +1060,29 @@ _cairo_gstate_show_page (cairo_gstate_t 
     return status;
 }
 
+static void
+_cairo_gstate_traps_extents_to_user_rectangle (cairo_gstate_t	  *gstate,
+                                               cairo_traps_t      *traps,
+                                               double *x1, double *y1,
+                                               double *x2, double *y2)
+{
+    cairo_box_t extents;
+
+    _cairo_traps_extents (traps, &extents);
+
+    if (extents.p1.x >= extents.p2.x || extents.p1.y >= extents.p2.y) {
+        /* no traps, so we actually won't draw anything */
+        *x1 = *y1 = *x2 = *y2 = 0;
+    } else {
+        *x1 = _cairo_fixed_to_double (extents.p1.x);
+        *y1 = _cairo_fixed_to_double (extents.p1.y);
+        *x2 = _cairo_fixed_to_double (extents.p2.x);
+        *y2 = _cairo_fixed_to_double (extents.p2.y);
+
+        _cairo_gstate_backend_to_user_rectangle (gstate, x1, y1, x2, y2, NULL);
+    }
+}
+
 cairo_status_t
 _cairo_gstate_stroke_extents (cairo_gstate_t	 *gstate,
 			      cairo_path_fixed_t *path,
@@ -1049,7 +1091,6 @@ _cairo_gstate_stroke_extents (cairo_gsta
 {
     cairo_status_t status;
     cairo_traps_t traps;
-    cairo_box_t extents;
 
     _cairo_traps_init (&traps);
 
@@ -1059,20 +1100,10 @@ _cairo_gstate_stroke_extents (cairo_gsta
 						&gstate->ctm_inverse,
 						gstate->tolerance,
 						&traps);
-    if (status)
-	goto BAIL;
-
-    _cairo_traps_extents (&traps, &extents);
-
-    *x1 = _cairo_fixed_to_double (extents.p1.x);
-    *y1 = _cairo_fixed_to_double (extents.p1.y);
-    *x2 = _cairo_fixed_to_double (extents.p2.x);
-    *y2 = _cairo_fixed_to_double (extents.p2.y);
-
-    _cairo_gstate_backend_to_user (gstate, x1, y1);
-    _cairo_gstate_backend_to_user (gstate, x2, y2);
+    if (status == CAIRO_STATUS_SUCCESS) {
+        _cairo_gstate_traps_extents_to_user_rectangle(gstate, &traps, x1, y1, x2, y2);
+    }
 
-BAIL:
     _cairo_traps_fini (&traps);
 
     return status;
@@ -1094,20 +1125,10 @@ _cairo_gstate_fill_extents (cairo_gstate
 					      gstate->fill_rule,
 					      gstate->tolerance,
 					      &traps);
-    if (status)
-	goto BAIL;
-
-    _cairo_traps_extents (&traps, &extents);
-
-    *x1 = _cairo_fixed_to_double (extents.p1.x);
-    *y1 = _cairo_fixed_to_double (extents.p1.y);
-    *x2 = _cairo_fixed_to_double (extents.p2.x);
-    *y2 = _cairo_fixed_to_double (extents.p2.y);
-
-    _cairo_gstate_backend_to_user (gstate, x1, y1);
-    _cairo_gstate_backend_to_user (gstate, x2, y2);
+    if (status == CAIRO_STATUS_SUCCESS) {
+        _cairo_gstate_traps_extents_to_user_rectangle(gstate, &traps, x1, y1, x2, y2);
+    }
 
-BAIL:
     _cairo_traps_fini (&traps);
 
     return status;
diff --git a/src/cairo.c b/src/cairo.c
index 0eb75cc..11a3190 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -2063,6 +2063,19 @@ cairo_in_fill (cairo_t *cr, double x, do
     return inside;
 }
 
+/**
+ * cairo_stroke_extents:
+ * @cr: a cairo context
+ * @x1: left of the resulting extents
+ * @y1: top of the resulting extents
+ * @x2: right of the resulting extents
+ * @y2: bottom of the resulting extents
+ *
+ * Computes a bounding box in user coordinates covering all area that will
+ * be stroked by the current path with the current stroking parameters. If
+ * the current path is empty, returns an empty rectangle. Surface dimensions
+ * and clipping are not taken into account.
+ **/
 void
 cairo_stroke_extents (cairo_t *cr,
                       double *x1, double *y1, double *x2, double *y2)
@@ -2077,6 +2090,19 @@ cairo_stroke_extents (cairo_t *cr,
 	_cairo_set_error (cr, cr->status);
 }
 
+/**
+ * cairo_fill_extents:
+ * @cr: a cairo context
+ * @x1: left of the resulting extents
+ * @y1: top of the resulting extents
+ * @x2: right of the resulting extents
+ * @y2: bottom of the resulting extents
+ *
+ * Computes a bounding box in user coordinates covering all area that will
+ * be filled by the current path. If the current path is empty, returns an
+ * empty rectangle. Surface dimensions and clipping are not taken into
+ * account.
+ **/
 void
 cairo_fill_extents (cairo_t *cr,
                     double *x1, double *y1, double *x2, double *y2)
diff --git a/src/cairoint.h b/src/cairoint.h
index 67227dc..23a7750 100755
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -1281,6 +1281,12 @@ _cairo_gstate_user_to_backend (cairo_gst
 cairo_private void
 _cairo_gstate_backend_to_user (cairo_gstate_t *gstate, double *x, double *y);
 
+cairo_private void
+_cairo_gstate_backend_to_user_rectangle (cairo_gstate_t *gstate,
+                                         double *x1, double *y1,
+                                         double *x2, double *y2,
+                                         cairo_bool_t *is_tight);
+
 cairo_private cairo_status_t
 _cairo_gstate_paint (cairo_gstate_t *gstate);
 
diff-tree 37fa632e59b7325041f689bf1a56e08d04379c96 (from de1915ffd2fe7f973529104a1041b33f2abfdfed)
Author: Robert O'Callahan <robert at ocallahan.org>
Date:   Mon Sep 25 23:14:43 2006 -0700

    Fix _cairo_matrix_transform_bounding_box to return tightness info
    
    Add return is_tight value to the internal function, indicating whether
    the transformed bounds still remain axis-aligned.

diff --git a/src/cairo-matrix.c b/src/cairo-matrix.c
index 60cdca3..339079f 100644
--- a/src/cairo-matrix.c
+++ b/src/cairo-matrix.c
@@ -358,7 +358,8 @@ slim_hidden_def(cairo_matrix_transform_p
 void
 _cairo_matrix_transform_bounding_box (const cairo_matrix_t *matrix,
 				      double *x, double *y,
-				      double *width, double *height)
+				      double *width, double *height,
+				      cairo_bool_t *is_tight)
 {
     int i;
     double quad_x[4], quad_y[4];
@@ -405,6 +406,21 @@ _cairo_matrix_transform_bounding_box (co
     *y = min_y;
     *width = max_x - min_x;
     *height = max_y - min_y;
+    
+    if (is_tight) {
+        /* it's tight if and only if the four corner points form an axis-aligned
+           rectangle.
+           And that's true if and only if we can derive corners 0 and 3 from
+           corners 1 and 2 in one of two straightforward ways...
+           We could use a tolerance here but for now we'll fall back to FALSE in the case
+           of floating point error.
+        */
+        *is_tight =
+            (quad_x[1] == quad_x[0] && quad_y[1] == quad_y[3] &&
+             quad_x[2] == quad_x[3] && quad_y[2] == quad_y[0]) ||
+            (quad_x[1] == quad_x[3] && quad_y[1] == quad_y[0] &&
+             quad_x[2] == quad_x[0] && quad_y[2] == quad_y[3]);
+    }
 }
 
 static void
diff --git a/src/cairoint.h b/src/cairoint.h
index 2d1d455..67227dc 100755
--- a/src/cairoint.h
+++ b/src/cairoint.h
@@ -2140,7 +2140,8 @@ _cairo_matrix_get_affine (const cairo_ma
 cairo_private void
 _cairo_matrix_transform_bounding_box (const cairo_matrix_t *matrix,
 				      double *x, double *y,
-				      double *width, double *height);
+				      double *width, double *height,
+				      cairo_bool_t *is_tight);
 
 cairo_private void
 _cairo_matrix_compute_determinant (const cairo_matrix_t *matrix, double *det);


More information about the cairo-commit mailing list