[cairo-commit] 4 commits - src/cairo.c src/cairo-path-fixed.c src/cairo-path-fixed-private.h src/cairo-path-stroke.c test/ft-text-vertical-layout-type1.c test/ft-text-vertical-layout-type3-ref.png test/ft-text-vertical-layout-type3-svg-ref.png test/.gitignore test/Makefile.am test/rectilinear-stroke.c test/rectilinear-stroke-ref.png

Carl Worth cworth at kemper.freedesktop.org
Fri Dec 22 18:00:50 PST 2006


 src/cairo-path-fixed-private.h                 |    3 
 src/cairo-path-fixed.c                         |    4 
 src/cairo-path-stroke.c                        |  318 ++++++++++++++++++++++++-
 src/cairo.c                                    |   11 
 test/.gitignore                                |    1 
 test/Makefile.am                               |    3 
 test/ft-text-vertical-layout-type1.c           |    3 
 test/ft-text-vertical-layout-type3-ref.png     |binary
 test/ft-text-vertical-layout-type3-svg-ref.png |binary
 test/rectilinear-stroke-ref.png                |binary
 test/rectilinear-stroke.c                      |  138 ++++++++++
 11 files changed, 473 insertions(+), 8 deletions(-)

New commits:
diff-tree ba531642f79d492ecbad8f086f1e44b56e157e36 (from b1189118532a1fe93e126843af739809d38a62bd)
Author: Carl Worth <cworth at cworth.org>
Date:   Tue Dec 19 21:34:16 2006 -0800

    Add optimization for rectilinear stroke
    
    This custom stroking code allows backends to use optimized region-based
    drawing operations for rectilinear strokes. This results in a 5-25x
    performance improvement when drawing rectilinear shapes:
    
    image-rgb          box-outline-stroke-100 0.18 -> 0.01: 25.58x speedup
    ████████████████████████▋
    image-rgba         box-outline-stroke-100 0.18 -> 0.01: 25.57x speedup
    ████████████████████████▋
     xlib-rgb          box-outline-stroke-100 0.49 -> 0.06:  8.67x speedup
    ███████▋
     xlib-rgba         box-outline-stroke-100 0.22 -> 0.04:  5.39x speedup
    ████▍
    
    In other words, using cairo_stroke instead of cairo_fill to draw the
    same shape was 5-15x slower before, but is 1.2-2x faster now.

diff --git a/src/cairo-path-fixed-private.h b/src/cairo-path-fixed-private.h
index 8e0d530..12ca618 100644
--- a/src/cairo-path-fixed-private.h
+++ b/src/cairo-path-fixed-private.h
@@ -68,7 +68,8 @@ struct _cairo_path_fixed {
 
     cairo_point_t last_move_point;
     cairo_point_t current_point;
-    unsigned int has_current_point : 1;
+    unsigned int has_current_point	: 1;
+    unsigned int has_curve_to		: 1;
 };
 
 #endif /* CAIRO_PATH_FIXED_PRIVATE_H */
diff --git a/src/cairo-path-fixed.c b/src/cairo-path-fixed.c
index 9549f8d..71d646b 100644
--- a/src/cairo-path-fixed.c
+++ b/src/cairo-path-fixed.c
@@ -88,6 +88,7 @@ _cairo_path_fixed_init (cairo_path_fixed
     path->current_point.x = 0;
     path->current_point.y = 0;
     path->has_current_point = FALSE;
+    path->has_curve_to = FALSE;
     path->last_move_point = path->current_point;
 }
 
@@ -101,6 +102,7 @@ _cairo_path_fixed_init_copy (cairo_path_
     _cairo_path_fixed_init (path);
     path->current_point = other->current_point;
     path->has_current_point = other->has_current_point;
+    path->has_curve_to = other->has_curve_to;
     path->last_move_point = other->last_move_point;
 
     for (other_op_buf = other->op_buf_head;
@@ -164,6 +166,7 @@ _cairo_path_fixed_fini (cairo_path_fixed
     path->arg_buf_tail = NULL;
 
     path->has_current_point = FALSE;
+    path->has_curve_to = FALSE;
 }
 
 void
@@ -289,6 +292,7 @@ _cairo_path_fixed_curve_to (cairo_path_f
 
     path->current_point = point[2];
     path->has_current_point = TRUE;
+    path->has_curve_to = TRUE;
 
     return CAIRO_STATUS_SUCCESS;
 }
diff --git a/src/cairo-path-stroke.c b/src/cairo-path-stroke.c
index ced31c1..63f0f4d 100644
--- a/src/cairo-path-stroke.c
+++ b/src/cairo-path-stroke.c
@@ -35,6 +35,7 @@
  */
 
 #include "cairoint.h"
+#include "cairo-path-fixed-private.h"
 
 typedef struct cairo_stroker {
     cairo_stroke_style_t	*style;
@@ -943,6 +944,12 @@ _cairo_stroker_close_path (void *closure
     return CAIRO_STATUS_SUCCESS;
 }
 
+static cairo_int_status_t
+_cairo_path_fixed_stroke_rectilinear (cairo_path_fixed_t	*path,
+				      cairo_stroke_style_t	*stroke_style,
+				      cairo_matrix_t		*ctm,
+				      cairo_traps_t		*traps);
+
 cairo_status_t
 _cairo_path_fixed_stroke_to_traps (cairo_path_fixed_t	*path,
 				   cairo_stroke_style_t	*stroke_style,
@@ -951,9 +958,21 @@ _cairo_path_fixed_stroke_to_traps (cairo
 				   double		 tolerance,
 				   cairo_traps_t	*traps)
 {
-    cairo_status_t status = CAIRO_STATUS_SUCCESS;
+    cairo_status_t status;
     cairo_stroker_t stroker;
 
+    /* Before we do anything else, we attempt the rectilinear
+     * stroker. It's careful to generate trapezoids that align to
+     * device-pixel boundaries when possible. Many backends can render
+     * those much faster than non-aligned trapezoids, (by using clip
+     * regions, etc.) */
+    status = _cairo_path_fixed_stroke_rectilinear (path,
+						   stroke_style,
+						   ctm,
+						   traps);
+    if (status != CAIRO_INT_STATUS_UNSUPPORTED)
+	return status;
+
     _cairo_stroker_init (&stroker, stroke_style,
 			 ctm, ctm_inverse, tolerance,
 			 traps);
@@ -984,3 +1003,300 @@ BAIL:
 
     return status;
 }
+
+typedef struct _cairo_rectilinear_stroker
+{
+    cairo_stroke_style_t *stroke_style;
+    cairo_fixed_t half_line_width;
+    cairo_traps_t *traps;
+    cairo_point_t current_point;
+    cairo_point_t first_point;
+    cairo_bool_t open_sub_path;
+    cairo_line_t *segments;
+    int segments_size;
+    int num_segments;
+} cairo_rectilinear_stroker_t;
+
+static void
+_cairo_rectilinear_stroker_init (cairo_rectilinear_stroker_t	*stroker,
+				 cairo_stroke_style_t		*stroke_style,
+				 cairo_traps_t			*traps)
+{
+    stroker->stroke_style = stroke_style;
+    stroker->half_line_width =
+	_cairo_fixed_from_double (stroke_style->line_width / 2.0);
+    stroker->traps = traps;
+    stroker->open_sub_path = FALSE;
+    stroker->segments = NULL;
+    stroker->segments_size = 0;
+    stroker->num_segments = 0;
+}
+
+static void
+_cairo_rectilinear_stroker_fini (cairo_rectilinear_stroker_t	*stroker)
+{
+    free (stroker->segments);
+}
+
+static cairo_status_t
+_cairo_rectilinear_stroker_add_segment (cairo_rectilinear_stroker_t	*stroker,
+					cairo_point_t			*p1,
+					cairo_point_t			*p2)
+{
+    int new_size;
+    cairo_line_t *new_segments;
+
+    if (stroker->num_segments == stroker->segments_size) {
+	new_size = stroker->segments_size * 2;
+	/* Common case is one rectangle of exactly 4 segments. */
+	if (new_size == 0)
+	    new_size = 4;
+	new_segments = realloc (stroker->segments, new_size * sizeof (cairo_line_t));
+	if (new_segments == NULL)
+	    return CAIRO_STATUS_NO_MEMORY;
+	stroker->segments_size = new_size;
+	stroker->segments = new_segments;
+    }
+
+    stroker->segments[stroker->num_segments].p1 = *p1;
+    stroker->segments[stroker->num_segments].p2 = *p2;
+    stroker->num_segments++;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_cairo_rectilinear_stroker_emit_segments (cairo_rectilinear_stroker_t *stroker)
+{
+    cairo_status_t status;
+    cairo_line_cap_t line_cap = stroker->stroke_style->line_cap;
+    cairo_fixed_t half_line_width = stroker->half_line_width;
+    cairo_bool_t lengthen_initial, shorten_final, lengthen_final;
+    cairo_point_t *a, *b;
+    cairo_point_t r[4];
+    int i;
+
+    for (i = 0; i < stroker->num_segments; i++) {
+	a = &stroker->segments[i].p1;
+	b = &stroker->segments[i].p2;
+
+	/* For each segment we generate a single rectangular
+	 * trapezoid. This rectangle is based on a perpendicular
+	 * extension (by half the line width) of the segment endpoints
+	 * after some adjustments of the endpoints to account for caps
+	 * and joins.
+	 */
+
+	/* We adjust the initial point of the segment to extend the
+	 * rectangle to include the previous cap or join, (this
+	 * adjustment applies to all segments except for the first
+	 * segment of open, butt-capped paths).
+	 */
+	lengthen_initial = TRUE;
+	if (i == 0 && stroker->open_sub_path && line_cap == CAIRO_LINE_CAP_BUTT)
+	    lengthen_initial = FALSE;
+
+	/* The adjustment of the final point is trickier. For all but
+	 * the last segment we shorten the segment at the final
+	 * endpoint to not overlap with the subsequent join. For the
+	 * last segment we do the same shortening if the path is
+	 * closed. If the path is open and butt-capped we do no
+	 * adjustment, while if it's open and square-capped we do a
+	 * lengthening adjustment instead to include the cap.
+	 */
+	shorten_final = TRUE;
+	lengthen_final = FALSE;
+	if (i == stroker->num_segments - 1 && stroker->open_sub_path) {
+	    shorten_final = FALSE;
+	    if (line_cap == CAIRO_LINE_CAP_SQUARE)
+		lengthen_final = TRUE;
+	}
+
+	/* Perform the adjustments of the endpoints. */
+	if (a->y == b->y) {
+	    if (a->x < b->x) {
+		if (lengthen_initial)
+		    a->x -= half_line_width;
+		if (shorten_final)
+		    b->x -= half_line_width;
+		else if (lengthen_final)
+		    b->x += half_line_width;
+	    } else {
+		if (lengthen_initial)
+		    a->x += half_line_width;
+		if (shorten_final)
+		    b->x += half_line_width;
+		else if (lengthen_final)
+		    b->x -= half_line_width;
+	    }
+	} else {
+	    if (a->y < b->y) {
+		if (lengthen_initial)
+		    a->y -= half_line_width;
+		if (shorten_final)
+		    b->y -= half_line_width;
+		else if (lengthen_final)
+		    b->y += half_line_width;
+	    } else {
+		if (lengthen_initial)
+		    a->y += half_line_width;
+		if (shorten_final)
+		    b->y += half_line_width;
+		else if (lengthen_final)
+		    b->y -= half_line_width;
+	    }
+	}
+
+	/* Form the rectangle by expanding by half the line width in
+	 * either perdendicular direction. */
+	r[0] = *a;
+	r[1] = *b;
+	r[2] = *b;
+	r[3] = *a;
+	if (a->y == b->y) {
+	    r[0].y -= half_line_width;
+	    r[1].y -= half_line_width;
+	    r[2].y += half_line_width;
+	    r[3].y += half_line_width;
+	} else {
+	    r[0].x -= half_line_width;
+	    r[1].x -= half_line_width;
+	    r[2].x += half_line_width;
+	    r[3].x += half_line_width;
+	}
+
+	status = _cairo_traps_tessellate_convex_quad (stroker->traps, r);
+	if (status)
+	    return status;
+    }
+
+    stroker->num_segments = 0;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_cairo_rectilinear_stroker_move_to (void		*closure,
+				    cairo_point_t	*point)
+{
+    cairo_rectilinear_stroker_t *stroker = closure;
+    cairo_status_t status;
+
+    status = _cairo_rectilinear_stroker_emit_segments (stroker);
+    if (status)
+	return status;
+
+    stroker->current_point = *point;
+    stroker->first_point = *point;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_status_t
+_cairo_rectilinear_stroker_line_to (void		*closure,
+				    cairo_point_t	*point)
+{
+    cairo_rectilinear_stroker_t *stroker = closure;
+    cairo_point_t *a = &stroker->current_point;
+    cairo_point_t *b = point;
+    cairo_status_t status;
+
+    /* We only support horizontal or vertical elements. */
+    if (! (a->x == b->x || a->y == b->y))
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+
+    /* We don't draw anything for degenerate paths. */
+    if (a->x == b->x && a->y == b->y)
+	return CAIRO_STATUS_SUCCESS;
+
+    status = _cairo_rectilinear_stroker_add_segment (stroker,
+						     a, b);
+
+    stroker->current_point = *point;
+    stroker->open_sub_path = TRUE;
+
+    return status;
+}
+
+static cairo_status_t
+_cairo_rectilinear_stroker_close_path (void *closure)
+{
+    cairo_rectilinear_stroker_t *stroker = closure;
+    cairo_status_t status;
+
+    /* We don't draw anything for degenerate paths. */
+    if (! stroker->open_sub_path)
+	return CAIRO_STATUS_SUCCESS;
+
+    status = _cairo_rectilinear_stroker_line_to (stroker,
+						 &stroker->first_point);
+    if (status)
+	return status;
+
+    stroker->open_sub_path = FALSE;
+
+    status = _cairo_rectilinear_stroker_emit_segments (stroker);
+    if (status)
+	return status;
+
+    return CAIRO_STATUS_SUCCESS;
+}
+
+static cairo_int_status_t
+_cairo_path_fixed_stroke_rectilinear (cairo_path_fixed_t	*path,
+				      cairo_stroke_style_t	*stroke_style,
+				      cairo_matrix_t		*ctm,
+				      cairo_traps_t		*traps)
+{
+    cairo_rectilinear_stroker_t rectilinear_stroker;
+    cairo_int_status_t status;
+
+    /* This special-case rectilinear stroker only supports
+     * miter-joined lines (not curves) and no dashing and a
+     * translation-only matrix (though it could probably be extended
+     * to support a matrix with uniform, integer sacling).
+     *
+     * It also only supports horizontal and vertical line_to
+     * elements. But we don't catch that here, but instead return
+     * UNSUPPORTED from _cairo_rectilinear_stroker_line_to if any
+     * non-rectilinear line_to is encountered.
+     */
+    if (path->has_curve_to)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+    if (stroke_style->line_join	!= CAIRO_LINE_JOIN_MITER)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+    if (stroke_style->dash)
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+    if (! (stroke_style->line_cap == CAIRO_LINE_CAP_BUTT ||
+	   stroke_style->line_cap == CAIRO_LINE_CAP_SQUARE))
+    {
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+    }
+    if (! (_cairo_matrix_is_identity (ctm) ||
+	   _cairo_matrix_is_translation (ctm)))
+    {
+	return CAIRO_INT_STATUS_UNSUPPORTED;
+    }
+
+    _cairo_rectilinear_stroker_init (&rectilinear_stroker, stroke_style, traps);
+
+    status = _cairo_path_fixed_interpret (path,
+					  CAIRO_DIRECTION_FORWARD,
+					  _cairo_rectilinear_stroker_move_to,
+					  _cairo_rectilinear_stroker_line_to,
+					  NULL,
+					  _cairo_rectilinear_stroker_close_path,
+					  &rectilinear_stroker);
+    if (status) {
+	_cairo_traps_fini (traps);
+	return status;
+    }
+
+    status = _cairo_rectilinear_stroker_emit_segments (&rectilinear_stroker);
+    if (status)
+	return status;
+
+    _cairo_rectilinear_stroker_fini (&rectilinear_stroker);
+
+    return CAIRO_STATUS_SUCCESS;
+}
diff --git a/src/cairo.c b/src/cairo.c
index 5d77ce3..d842d75 100644
--- a/src/cairo.c
+++ b/src/cairo.c
@@ -48,11 +48,12 @@ static const cairo_t cairo_nil = {
   CAIRO_REF_COUNT_INVALID,	/* ref_count */
   CAIRO_STATUS_NO_MEMORY,	/* status */
   { 				/* path */
-    NULL, NULL,			/* op_buf_head, op_buf_tail */
-    NULL, NULL,			/* arg_buf_head, arg_buf_tail */
-    { 0, 0 },			/* last_move_point */
-    { 0, 0 },			/* current point */
-    FALSE,			/* has_current_point */
+    NULL, NULL,			   /* op_buf_head, op_buf_tail */
+    NULL, NULL,			   /* arg_buf_head, arg_buf_tail */
+    { 0, 0 },			   /* last_move_point */
+    { 0, 0 },			   /* current point */
+    FALSE,			   /* has_current_point */
+    FALSE			   /* has_curve_to */
   },
   NULL				/* gstate */
 };
diff --git a/test/ft-text-vertical-layout-type3-ref.png b/test/ft-text-vertical-layout-type3-ref.png
index eaee4f0..8ec2ebe 100644
Binary files a/test/ft-text-vertical-layout-type3-ref.png and b/test/ft-text-vertical-layout-type3-ref.png differ
diff --git a/test/ft-text-vertical-layout-type3-svg-ref.png b/test/ft-text-vertical-layout-type3-svg-ref.png
index a01c9d3..7aa322d 100644
Binary files a/test/ft-text-vertical-layout-type3-svg-ref.png and b/test/ft-text-vertical-layout-type3-svg-ref.png differ
diff-tree b1189118532a1fe93e126843af739809d38a62bd (from 7b1509f4f37118d14bd5d70365d1608ead5e2819)
Author: Carl Worth <cworth at cworth.org>
Date:   Fri Dec 22 17:11:08 2006 -0800

    Put ft-text-vertical-layout-type1 back on the XFAIL list
    
    I must not have the right font available, (test result is coming out
    looking like the result of ft-text-vertical-layout-type3, Vera?).
    
    We should switch this test to load a bundled font, (should do that for
    all font-using tests, too).

diff --git a/test/Makefile.am b/test/Makefile.am
index 4ed21d7..16237dc 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -347,6 +347,7 @@ a8-mask				\
 big-trap			\
 extend-reflect			\
 filter-nearest-offset		\
+ft-text-vertical-layout-type1	\
 leaky-dash			\
 long-lines			\
 self-intersecting		\
diff --git a/test/ft-text-vertical-layout-type1.c b/test/ft-text-vertical-layout-type1.c
index 82e31f1..c52c28d 100644
--- a/test/ft-text-vertical-layout-type1.c
+++ b/test/ft-text-vertical-layout-type1.c
@@ -37,7 +37,8 @@ static cairo_test_draw_function_t draw;
 
 cairo_test_t test = {
     "ft-text-vertical-layout-type1",
-    "Tests text rendering for vertical layout with Type1 fonts",
+    "Tests text rendering for vertical layout with Type1 fonts"
+    "\nCan fail if an incorrect font is loaded---need to bundle the desired font",
     WIDTH, HEIGHT,
     draw
 };
diff-tree 7b1509f4f37118d14bd5d70365d1608ead5e2819 (from 9d2d3b95e359cd2829c8d02a378dbfec2065e832)
Author: Carl Worth <cworth at cworth.org>
Date:   Tue Dec 19 13:13:11 2006 -0800

    Reimplement path.has_current point as a 1-bit bitfield

diff --git a/src/cairo-path-fixed-private.h b/src/cairo-path-fixed-private.h
index e8e0df1..8e0d530 100644
--- a/src/cairo-path-fixed-private.h
+++ b/src/cairo-path-fixed-private.h
@@ -68,7 +68,7 @@ struct _cairo_path_fixed {
 
     cairo_point_t last_move_point;
     cairo_point_t current_point;
-    int has_current_point;
+    unsigned int has_current_point : 1;
 };
 
 #endif /* CAIRO_PATH_FIXED_PRIVATE_H */
diff-tree 9d2d3b95e359cd2829c8d02a378dbfec2065e832 (from 39ce31ade64e1c3b9e5880134ab77ca96f48f1b9)
Author: Carl Worth <cworth at cworth.org>
Date:   Thu Dec 21 08:20:20 2006 -0800

    Add new rectilinear-stroke test
    
    This is in preparation for an optimized implementation of cairo_stroke
    for rectilinear paths.

diff --git a/test/.gitignore b/test/.gitignore
index 489bce6..a9f71be 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -104,6 +104,7 @@ svg-surface.svg
 pixman-rotate
 pthread-show-text
 rectangle-rounding-error
+rectilinear-stroke
 rel-path
 scale-source-surface-paint
 select-font-face
diff --git a/test/Makefile.am b/test/Makefile.am
index 6dd82cb..4ed21d7 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -71,6 +71,7 @@ pattern-getters			\
 pixman-rotate			\
 random-intersections		\
 rectangle-rounding-error	\
+rectilinear-stroke		\
 scale-source-surface-paint	\
 select-font-face		\
 select-font-no-show-text	\
@@ -286,6 +287,7 @@ random-intersections-ref.png				\
 random-intersections-rgb24-ref.png			\
 random-intersections-ps-argb32-ref.png			\
 rectangle-rounding-error-ref.png			\
+rectilinear-stroke-ref.png				\
 rel-path-ref.png					\
 rel-path-rgb24-ref.png					\
 romedalen.png						\
diff --git a/test/rectilinear-stroke-ref.png b/test/rectilinear-stroke-ref.png
new file mode 100644
index 0000000..0a40b0d
Binary files /dev/null and b/test/rectilinear-stroke-ref.png differ
diff --git a/test/rectilinear-stroke.c b/test/rectilinear-stroke.c
new file mode 100644
index 0000000..6ba3f1a
--- /dev/null
+++ b/test/rectilinear-stroke.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright © 2006 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies
+ * of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * Author: Carl D. Worth <cworth at cworth.org>
+ */
+
+#include "cairo-test.h"
+
+#define SIZE 25
+
+static cairo_test_draw_function_t draw;
+
+cairo_test_t test = {
+    "rectilinear-stroke",
+    "Test rectilinear stroke operations (covering only whole pixels)",
+    SIZE, SIZE,
+    draw
+};
+
+static cairo_test_status_t
+draw (cairo_t *cr, int width, int height)
+{
+    /* Paint background white, then draw in black. */
+    cairo_set_source_rgb (cr, 1.0, 1.0, 1.0); /* white */
+    cairo_paint (cr);
+    cairo_set_source_rgb (cr, 0.0, 0.0, 0.0); /* black */
+
+    cairo_set_line_width (cr, 1.0);
+    cairo_translate (cr, 1, 1);
+
+    /* Draw everything first with square caps. */
+    cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
+
+    /* Draw horizontal and vertical segments, each in both
+     * directions. */
+    cairo_move_to     (cr,  4.5,  0.5);
+    cairo_rel_line_to (cr,  2.0,  0.0);
+
+    cairo_move_to     (cr, 10.5,  4.5);
+    cairo_rel_line_to (cr,  0.0,  2.0);
+
+    cairo_move_to     (cr,  6.5, 10.5);
+    cairo_rel_line_to (cr, -2.0,  0.0);
+
+    cairo_move_to     (cr,  0.5,  6.5);
+    cairo_rel_line_to (cr,  0.0, -2.0);
+
+    /* Draw right angle turns in four directions. */
+    cairo_move_to     (cr,  0.5,  2.5);
+    cairo_rel_line_to (cr,  0.0, -2.0);
+    cairo_rel_line_to (cr,  2.0,  0.0);
+
+    cairo_move_to     (cr,  8.5,  0.5);
+    cairo_rel_line_to (cr,  2.0,  0.0);
+    cairo_rel_line_to (cr,  0.0,  2.0);
+
+    cairo_move_to     (cr, 10.5,  8.5);
+    cairo_rel_line_to (cr,  0.0,  2.0);
+    cairo_rel_line_to (cr, -2.0,  0.0);
+
+    cairo_move_to     (cr,  2.5, 10.5);
+    cairo_rel_line_to (cr, -2.0,  0.0);
+    cairo_rel_line_to (cr,  0.0, -2.0);
+
+    /* Draw a closed-path rectangle */
+    cairo_rectangle   (cr, 0.5, 12.5, 10.0, 10.0);
+
+    cairo_stroke (cr);
+
+    cairo_translate (cr, 12, 0);
+
+    /* Now draw the same results, but with butt caps. */
+    cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
+
+    /* Draw horizontal and vertical segments, each in both
+     * directions. */
+    cairo_move_to     (cr,  4.0,  0.5);
+    cairo_rel_line_to (cr,  3.0,  0.0);
+
+    cairo_move_to     (cr, 10.5,  4.0);
+    cairo_rel_line_to (cr,  0.0,  3.0);
+
+    cairo_move_to     (cr,  7.0, 10.5);
+    cairo_rel_line_to (cr, -3.0,  0.0);
+
+    cairo_move_to     (cr,  0.5,  7.0);
+    cairo_rel_line_to (cr,  0.0, -3.0);
+
+    /* Draw right angle turns in four directions. */
+    cairo_move_to     (cr,  0.5,  3.0);
+    cairo_rel_line_to (cr,  0.0, -2.5);
+    cairo_rel_line_to (cr,  2.5,  0.0);
+
+    cairo_move_to     (cr,  8.0,  0.5);
+    cairo_rel_line_to (cr,  2.5,  0.0);
+    cairo_rel_line_to (cr,  0.0,  2.5);
+
+    cairo_move_to     (cr, 10.5,  8.0);
+    cairo_rel_line_to (cr,  0.0,  2.5);
+    cairo_rel_line_to (cr, -2.5,  0.0);
+
+    cairo_move_to     (cr,  3.0, 10.5);
+    cairo_rel_line_to (cr, -2.5,  0.0);
+    cairo_rel_line_to (cr,  0.0, -2.5);
+
+    /* Draw a closed-path rectangle */
+    cairo_rectangle   (cr, 0.5, 12.5, 10.0, 10.0);
+
+    cairo_stroke (cr);
+
+    return CAIRO_TEST_SUCCESS;
+}
+
+int
+main (void)
+{
+    return cairo_test (&test);
+}


More information about the cairo-commit mailing list