diff --git a/src/cairo-path-stroke.c b/src/cairo-path-stroke.c index 2f8931434..4ba321ef2 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_sub_path; + cairo_bool_t has_current_face; cairo_stroke_face_t current_face; @@ -164,6 +166,7 @@ _cairo_stroker_init (cairo_stroker_t *stroker, stroker->has_current_face = FALSE; stroker->has_first_face = FALSE; + stroker->has_sub_path = FALSE; if (stroker->style->dash) _cairo_stroker_start_dash (stroker); @@ -205,7 +208,8 @@ _cairo_stroker_join (cairo_stroker_t *stroker, cairo_stroke_face_t *in, cairo_st if (in->cw.x == out->cw.x && in->cw.y == out->cw.y && in->ccw.x == out->ccw.x - && in->ccw.y == out->ccw.y) { + && in->ccw.y == out->ccw.y) + { return CAIRO_STATUS_SUCCESS; } @@ -459,10 +463,26 @@ _cairo_stroker_add_trailing_cap (cairo_stroker_t *stroker, return _cairo_stroker_add_cap (stroker, face); } +static void +_compute_face (cairo_point_t *point, cairo_slope_t *slope, cairo_stroker_t *stroker, cairo_stroke_face_t *face); + static cairo_status_t _cairo_stroker_add_caps (cairo_stroker_t *stroker) { cairo_status_t status; + /* check for a degenerative sub_path */ + if (stroker->has_sub_path + && !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); @@ -563,12 +583,8 @@ _cairo_stroker_add_sub_edge (cairo_stroker_t *stroker, cairo_point_t *p1, cairo_ fields from start. */ _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. */ + if (p1->x == p2->x && p1->y == p2->y) return CAIRO_STATUS_SUCCESS; - } /* XXX: I should really check the return value of the move_to/line_to functions here to catch out of memory @@ -609,8 +625,9 @@ _cairo_stroker_move_to (void *closure, cairo_point_t *point) stroker->first_point = *point; stroker->current_point = *point; - stroker->has_first_face = 0; - stroker->has_current_face = 0; + stroker->has_first_face = FALSE; + stroker->has_current_face = FALSE; + stroker->has_sub_path = FALSE; return CAIRO_STATUS_SUCCESS; } @@ -635,13 +652,10 @@ _cairo_stroker_line_to (void *closure, cairo_point_t *point) cairo_point_t *p2 = point; 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. */ + stroker->has_sub_path = TRUE; + + if (p1->x == p2->x && p1->y == p2->y) return CAIRO_STATUS_SUCCESS; - } _cairo_slope_init (&slope, p1, p2); @@ -656,11 +670,11 @@ _cairo_stroker_line_to (void *closure, cairo_point_t *point) } else { if (!stroker->has_first_face) { stroker->first_face = start; - stroker->has_first_face = 1; + stroker->has_first_face = TRUE; } } stroker->current_face = end; - stroker->has_current_face = 1; + stroker->has_current_face = TRUE; stroker->current_point = *point; @@ -679,19 +693,14 @@ _cairo_stroker_line_to_dashed (void *closure, cairo_point_t *point) double dx, dy; double dx2, dy2; cairo_point_t fd1, fd2; - int first = 1; + cairo_bool_t first = TRUE; cairo_stroke_face_t sub_start, sub_end; cairo_point_t *p1 = &stroker->current_point; cairo_point_t *p2 = point; 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. */ + if (p1->x == p2->x && p1->y == p2->y) return CAIRO_STATUS_SUCCESS; - } _cairo_slope_init (&slope, p1, p2); @@ -742,7 +751,7 @@ _cairo_stroker_line_to_dashed (void *closure, cairo_point_t *point) } else { if (!stroker->has_first_face) { stroker->first_face = sub_start; - stroker->has_first_face = 1; + stroker->has_first_face = TRUE; } else { status = _cairo_stroker_add_leading_cap (stroker, &sub_start); if (status) @@ -763,7 +772,7 @@ _cairo_stroker_line_to_dashed (void *closure, cairo_point_t *point) * through */ stroker->current_face = sub_end; - stroker->has_current_face = 1; + stroker->has_current_face = TRUE; } } else { /* @@ -778,11 +787,11 @@ _cairo_stroker_line_to_dashed (void *closure, cairo_point_t *point) } } if (!remain) - stroker->has_current_face = 0; + stroker->has_current_face = FALSE; } _cairo_stroker_step_dash (stroker, tmp); fd1 = fd2; - first = 0; + first = FALSE; } stroker->current_point = *point; @@ -822,11 +831,11 @@ _cairo_stroker_curve_to (void *closure, } else { if (!stroker->has_first_face) { stroker->first_face = start; - stroker->has_first_face = 1; + stroker->has_first_face = TRUE; } } stroker->current_face = end; - stroker->has_current_face = 1; + stroker->has_current_face = TRUE; extra_points[0] = start.cw; extra_points[0].x -= start.point.x; @@ -943,10 +952,15 @@ _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_first_face = 0; - stroker->has_current_face = 0; + stroker->has_sub_path = FALSE; + stroker->has_first_face = FALSE; + stroker->has_current_face = FALSE; return CAIRO_STATUS_SUCCESS; } diff --git a/src/cairo.c b/src/cairo.c index 7d6d4bc69..9b4154a52 100644 --- a/src/cairo.c +++ b/src/cairo.c @@ -900,6 +900,11 @@ cairo_set_line_join (cairo_t *cr, cairo_line_join_t line_join) * stroke. The @offset specifies an offset into the pattern at which * the stroke begins. * + * Each "on" segment will have caps applied as if the segment were a + * separate sub-path. In particular, it is valid to use an "on" length + * of 0.0 with CAIRO_LINE_CAP_ROUND or CAIRO_LINE_CAP_SQUARE in order + * to distributed dots or squares along a path. + * * Note: The length values are in user-space units as evaluated at the * time of stroking. This is not necessarily the same as the user * space at the time of cairo_set_dash(). @@ -1178,7 +1183,7 @@ slim_hidden_def(cairo_new_path); * @x: the X coordinate of the new position * @y: the Y coordinate of the new position * - * Begin a new subpath. After this call the current point will be (@x, + * Begin a new sub-path. After this call the current point will be (@x, * @y). **/ void @@ -1203,14 +1208,14 @@ slim_hidden_def(cairo_move_to); * cairo_new_sub_path: * @cr: a cairo context * - * Begin a new subpath. Note that the existing path is not + * Begin a new sub-path. Note that the existing path is not * affected. After this call there will be no current point. * - * In many cases, this call is not needed since new subpaths are + * In many cases, this call is not needed since new sub-paths are * frequently started with cairo_move_to(). * * A call to cairo_new_sub_path() is particularly useful when - * beginning a new subpath with one of the cairo_arc() calls. This + * beginning a new sub-path with one of the cairo_arc() calls. This * makes things easier as it is no longer necessary to manually * compute the arc's initial coordinates for a call to * cairo_move_to(). @@ -1442,7 +1447,7 @@ cairo_arc_to (cairo_t *cr, * @dx: the X offset * @dy: the Y offset * - * Begin a new subpath. After this call the current point will offset + * Begin a new sub-path. After this call the current point will offset * by (@x, @y). * * Given a current point of (x, y), cairo_rel_move_to(@cr, @dx, @dy) @@ -1573,7 +1578,7 @@ cairo_rel_curve_to (cairo_t *cr, * @width: the width of the rectangle * @height: the height of the rectangle * - * Adds a closed-subpath rectangle of the given size to the current + * Adds a closed sub-path rectangle of the given size to the current * path at position (@x, @y) in user-space coordinates. * * This function is logically equivalent to: @@ -1618,15 +1623,15 @@ cairo_stroke_to_path (cairo_t *cr) * @cr: a cairo context * * Adds a line segment to the path from the current point to the - * beginning of the current subpath, (the most recent point passed to - * cairo_move_to()), and closes this subpath. After this call the - * current point will be at the joined endpoint of the subpath. + * beginning of the current sub-path, (the most recent point passed to + * cairo_move_to()), and closes this sub-path. After this call the + * current point will be at the joined endpoint of the sub-path. * * The behavior of cairo_close_path() is distinct from simply calling * cairo_line_to() with the equivalent coordinate in the case of - * stroking. When a closed subpath is stroked, there are no caps on - * the ends of the subpath. Instead, there is a line join connecting - * the final and initial segments of the subpath. + * stroking. When a closed sub-path is stroked, there are no caps on + * the ends of the sub-path. Instead, there is a line join connecting + * the final and initial segments of the sub-path. * * If there is no current point before the call to cairo_close_path, * this function will have no effect. @@ -1776,6 +1781,27 @@ cairo_mask_surface (cairo_t *cr, * context. See cairo_set_line_width(), cairo_set_line_join(), * cairo_set_line_cap(), cairo_set_dash(), and * cairo_stroke_preserve(). + * + * Note: Degenerate segments and sub-paths are treated specially and + * provide a useful result. These can result in two different + * situations: + * + * 1. Zero-length "on" segments set in cairo_set_dash(). If the cap + * style is CAIRO_LINE_CAP_ROUND or CAIRO_LINE_CAP_SQUARE then these + * segments will be drawn as circular dots or squares respectively. In + * the case of CAIRO_LINE_CAP_SQUARE, the orientation of the squares + * is determined by the direction of the underlying path. + * + * 2. A sub-path created by cairo_move_to() followed by either a + * cairo_close_path() or one or more calls to cairo_line_to() to the + * same coordinate as the cairo_move_to(). If the cap style is + * CAIRO_LINE_CAP_ROUND then these sub-paths will be drawn as circular + * dots. Note that in the case of CAIRO_LINE_CAP_SQUARE a degenerate + * sub-path will not be drawn at all, (since the correct orientation + * is indeterminate). + * + * In no case will a cap style of CAIRO_LINE_CAP_BUTT cause anything + * to be drawn in the case of either degenerate segments or sub-paths. **/ void cairo_stroke (cairo_t *cr) diff --git a/test/Makefile.am b/test/Makefile.am index dcaa67e01..4da7ed445 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-ref.png b/test/degenerate-path-ref.png new file mode 100644 index 000000000..1b07de4e7 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 000000000..29f4089e6 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 000000000..1329bdfe4 --- /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 + */ + +#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