[cairo] Zero length segment and SVG

Jeff Muizelaar jeff at infidigm.net
Sat Jun 24 20:41:20 PDT 2006


On Tue, Jun 20, 2006 at 11:04:14AM -0700, Carl Worth wrote:
> Jeff,
> 
> Thanks for stepping into your role as the maintainer of the stroking
> code. You're doing a fine job! (And yes, it is rather hairy in
> spots---sorry about that.)
> 
> -Carl

Just an update. Here is my current patch. It should actually fix both of
the bugs pointed out by Keith Wells. It still needs more work though.

Note: the test case exercises a bug in the postscript surface that
causes it to split the ~> of a base85 stream across two lines.
Ghostscript doesn't like this very much.

-Jeff

diff --git a/src/cairo-path-stroke.c b/src/cairo-path-stroke.c
index 2f89314..adccda1 100644
--- a/src/cairo-path-stroke.c
+++ b/src/cairo-path-stroke.c
@@ -50,6 +50,8 @@ typedef struct cairo_stroker {
     cairo_point_t current_point;
     cairo_point_t first_point;
 
+    cairo_bool_t has_subpath;
+ 
     cairo_bool_t has_current_face;
     cairo_stroke_face_t current_face;
 
@@ -164,6 +166,7 @@ _cairo_stroker_init (cairo_stroker_t		*s
 
     stroker->has_current_face = FALSE;
     stroker->has_first_face = FALSE;
+    stroker->has_subpath = FALSE;
 
     if (stroker->style->dash)
 	_cairo_stroker_start_dash (stroker);
@@ -459,26 +462,6 @@ _cairo_stroker_add_trailing_cap (cairo_s
     return _cairo_stroker_add_cap (stroker, face);
 }
 
-static cairo_status_t
-_cairo_stroker_add_caps (cairo_stroker_t *stroker)
-{
-    cairo_status_t status;
-
-    if (stroker->has_first_face) {
-	status = _cairo_stroker_add_leading_cap (stroker, &stroker->first_face);
-	if (status)
-	    return status;
-    }
-
-    if (stroker->has_current_face) {
-	status = _cairo_stroker_add_trailing_cap (stroker, &stroker->current_face);
-	if (status)
-	    return status;
-    }
-
-    return CAIRO_STATUS_SUCCESS;
-}
-
 static void
 _compute_face (cairo_point_t *point, cairo_slope_t *slope, cairo_stroker_t *stroker, cairo_stroke_face_t *face)
 {
@@ -549,6 +532,37 @@ _compute_face (cairo_point_t *point, cai
 }
 
 static cairo_status_t
+_cairo_stroker_add_caps (cairo_stroker_t *stroker)
+{
+    cairo_status_t status;
+    /* check for a degenerative subpath */
+    if (stroker->has_subpath
+	&& !stroker->has_first_face
+	&& !stroker->has_current_face
+	&& stroker->style->line_cap == CAIRO_LINE_JOIN_ROUND) {
+	/* pick an arbitrary slope to use */
+	cairo_slope_t slope = {1, 0};
+	_compute_face (&stroker->first_point, &slope, stroker, &stroker->first_face);
+
+	stroker->has_first_face = stroker->has_current_face = TRUE;
+	stroker->current_face = stroker->first_face;
+    }
+    if (stroker->has_first_face) {
+	status = _cairo_stroker_add_leading_cap (stroker, &stroker->first_face);
+	if (status)
+	    return status;
+    }
+
+    if (stroker->has_current_face) {
+	status = _cairo_stroker_add_trailing_cap (stroker, &stroker->current_face);
+	if (status)
+	    return status;
+    }
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
 _cairo_stroker_add_sub_edge (cairo_stroker_t *stroker, cairo_point_t *p1, cairo_point_t *p2,
 			     cairo_slope_t *slope, cairo_stroke_face_t *start,
 			     cairo_stroke_face_t *end)
@@ -564,9 +578,6 @@ _cairo_stroker_add_sub_edge (cairo_strok
     _compute_face (p2, slope, stroker, end);
 
     if (p1->x == p2->x && p1->y == p2->y) {
-	/* XXX: Need to rethink how this case should be handled, (both
-           here and in _compute_face). The key behavior is that
-           degenerate paths should draw as much as possible. */
 	return CAIRO_STATUS_SUCCESS;
     }
 
@@ -611,6 +622,7 @@ _cairo_stroker_move_to (void *closure, c
 
     stroker->has_first_face = 0;
     stroker->has_current_face = 0;
+    stroker->has_subpath = 0;
 
     return CAIRO_STATUS_SUCCESS;
 }
@@ -635,11 +647,8 @@ _cairo_stroker_line_to (void *closure, c
     cairo_point_t *p2 = point;
     cairo_slope_t slope;
 
+    stroker->has_subpath = TRUE;
     if (p1->x == p2->x && p1->y == p2->y) {
-	/* XXX: Need to rethink how this case should be handled, (both
-           here and in cairo_stroker_add_sub_edge and in _compute_face). The
-           key behavior is that degenerate paths should draw as much
-           as possible. */
 	return CAIRO_STATUS_SUCCESS;
     }
 
@@ -686,10 +695,6 @@ _cairo_stroker_line_to_dashed (void *clo
     cairo_slope_t slope;
 
     if (p1->x == p2->x && p1->y == p2->y) {
-	/* XXX: Need to rethink how this case should be handled, (both
-           here and in cairo_stroker_add_sub_edge and in _compute_face). The
-           key behavior is that degenerate paths should draw as much
-           as possible. */
 	return CAIRO_STATUS_SUCCESS;
     }
 
@@ -943,8 +948,13 @@ _cairo_stroker_close_path (void *closure
 	status = _cairo_stroker_join (stroker, &stroker->current_face, &stroker->first_face);
 	if (status)
 	    return status;
+    } else {
+	status = _cairo_stroker_add_caps (stroker);
+	if (status)
+	    return status;
     }
 
+    stroker->has_subpath = 0;
     stroker->has_first_face = 0;
     stroker->has_current_face = 0;
 
diff --git a/src/cairo.c b/src/cairo.c
index 7d6d4bc..8296b21 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -1775,7 +1775,8 @@ cairo_mask_surface (cairo_t         *cr,
  * cairo_stroke, the current path will be cleared from the cairo
  * context. See cairo_set_line_width(), cairo_set_line_join(),
  * cairo_set_line_cap(), cairo_set_dash(), and
- * cairo_stroke_preserve().
+ * cairo_stroke_preserve(). Degenerative paths are only capped if the
+ * line cap is set to CAIRO_LINE_CAP_ROUND.
  **/
 void
 cairo_stroke (cairo_t *cr)
diff --git a/test/Makefile.am b/test/Makefile.am
index dcaa67e..4da7ed4 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -20,6 +20,7 @@ dash-caps-joins			\
 dash-scale			\
 dash-offset-negative		\
 dash-zero-length		\
+degenerate-path			\
 device-offset			\
 device-offset-positive		\
 extend-reflect			\
@@ -178,6 +179,9 @@ dash-offset-negative-ps-argb32-ref.png		
 dash-zero-length-ref.png				\
 dash-zero-length-rgb24-ref.png				\
 dash-zero-length-ps-argb32-ref.png			\
+degenerate-path-ref.png					\
+degenerate-path-rgb24-ref.png				\
+degenerate-path-ps-argb32-ref.png			\
 device-offset-ref.png					\
 device-offset-positive-ref.png				\
 fill-and-stroke-ref.png					\
diff --git a/test/degenerate-path-ps-argb32-ref.png b/test/degenerate-path-ps-argb32-ref.png
new file mode 100644
index 0000000..d947e5c
Binary files /dev/null and b/test/degenerate-path-ps-argb32-ref.png differ
diff --git a/test/degenerate-path-ref.png b/test/degenerate-path-ref.png
new file mode 100644
index 0000000..b99286d
Binary files /dev/null and b/test/degenerate-path-ref.png differ
diff --git a/test/degenerate-path-rgb24-ref.png b/test/degenerate-path-rgb24-ref.png
new file mode 100644
index 0000000..615a24f
Binary files /dev/null and b/test/degenerate-path-rgb24-ref.png differ
diff --git a/test/degenerate-path.c b/test/degenerate-path.c
new file mode 100644
index 0000000..2aeee07
--- /dev/null
+++ b/test/degenerate-path.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright © 2006 Jeff Muizelaar
+ *
+ * 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
+ * Jeff Muizelaar not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. Jeff Muizelaar makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * JEFF MUIZELAAR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL JEFF MUIZELAAR 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: Jeff Muizelaar <jeff at infidigm.net>
+ */
+
+#include "cairo-test.h"
+
+#define IMAGE_WIDTH 40
+#define IMAGE_HEIGHT 22
+
+cairo_test_t test = {
+    "degenerate-path",
+    "Tests the behaviour of degenerate paths with different cap types",
+    IMAGE_WIDTH, IMAGE_HEIGHT
+};
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
+
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height)
+{
+    const cairo_line_cap_t cap[] = { CAIRO_LINE_CAP_ROUND, CAIRO_LINE_CAP_SQUARE, CAIRO_LINE_CAP_BUTT };
+    int i;
+
+    cairo_set_source_rgb (cr, 1, 0, 0);
+
+    for (i=0; i<ARRAY_SIZE(cap); i++) {
+	cairo_set_line_cap (cr, cap[i]);
+
+	cairo_set_line_width (cr, 6);
+	cairo_move_to (cr, 6, 6);
+	cairo_line_to (cr, 6, 6);
+	cairo_stroke (cr);
+
+	cairo_move_to (cr, 6, 15);
+	cairo_close_path (cr);
+	cairo_stroke (cr);
+
+	cairo_translate (cr, 12, 0);
+    }
+    return CAIRO_TEST_SUCCESS;
+}
+
+int
+main (void)
+{
+    return cairo_test (&test, draw);
+}


More information about the cairo mailing list