cairo/src/cairo-path-stroke.c

1008 lines
28 KiB
C

/* cairo - a vector graphics library with display and print output
*
* Copyright © 2002 University of Southern California
*
* This library is free software; you can redistribute it and/or
* modify it either under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation
* (the "LGPL") or, at your option, under the terms of the Mozilla
* Public License Version 1.1 (the "MPL"). If you do not alter this
* notice, a recipient may use your version of this file under either
* the MPL or the LGPL.
*
* You should have received a copy of the LGPL along with this library
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
* You should have received a copy of the MPL along with this library
* in the file COPYING-MPL-1.1
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
* the specific language governing rights and limitations.
*
* The Original Code is the cairo graphics library.
*
* The Initial Developer of the Original Code is University of Southern
* California.
*
* Contributor(s):
* Carl D. Worth <cworth@cworth.org>
*/
#include "cairoint.h"
typedef struct cairo_stroker {
cairo_stroke_style_t *style;
cairo_matrix_t *ctm;
cairo_matrix_t *ctm_inverse;
double tolerance;
cairo_traps_t *traps;
cairo_pen_t pen;
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;
cairo_bool_t has_first_face;
cairo_stroke_face_t first_face;
cairo_bool_t dashed;
unsigned int dash_index;
int dash_on;
double dash_remain;
} cairo_stroker_t;
/* private functions */
static void
_cairo_stroker_init (cairo_stroker_t *stroker,
cairo_stroke_style_t *stroke_style,
cairo_matrix_t *ctm,
cairo_matrix_t *ctm_inverse,
double tolerance,
cairo_traps_t *traps);
static void
_cairo_stroker_fini (cairo_stroker_t *stroker);
static cairo_status_t
_cairo_stroker_move_to (void *closure, cairo_point_t *point);
static cairo_status_t
_cairo_stroker_line_to (void *closure, cairo_point_t *point);
static cairo_status_t
_cairo_stroker_line_to_dashed (void *closure, cairo_point_t *point);
static cairo_status_t
_cairo_stroker_curve_to (void *closure,
cairo_point_t *b,
cairo_point_t *c,
cairo_point_t *d);
static cairo_status_t
_cairo_stroker_curve_to_dashed (void *closure,
cairo_point_t *b,
cairo_point_t *c,
cairo_point_t *d);
static cairo_status_t
_cairo_stroker_close_path (void *closure);
static void
_translate_point (cairo_point_t *point, cairo_point_t *offset);
static int
_cairo_stroker_face_clockwise (cairo_stroke_face_t *in, cairo_stroke_face_t *out);
static cairo_status_t
_cairo_stroker_join (cairo_stroker_t *stroker, cairo_stroke_face_t *in, cairo_stroke_face_t *out);
static void
_cairo_stroker_start_dash (cairo_stroker_t *stroker)
{
double offset;
int on = 1;
unsigned int i = 0;
offset = stroker->style->dash_offset;
/* We stop searching for a starting point as soon as the
offset reaches zero. Otherwise when an initial dash
segment shrinks to zero it will be skipped over. */
while (offset > 0.0 && offset >= stroker->style->dash[i]) {
offset -= stroker->style->dash[i];
on = 1-on;
if (++i == stroker->style->num_dashes)
i = 0;
}
stroker->dashed = TRUE;
stroker->dash_index = i;
stroker->dash_on = on;
stroker->dash_remain = stroker->style->dash[i] - offset;
}
static void
_cairo_stroker_step_dash (cairo_stroker_t *stroker, double step)
{
stroker->dash_remain -= step;
if (stroker->dash_remain <= 0) {
stroker->dash_index++;
if (stroker->dash_index == stroker->style->num_dashes)
stroker->dash_index = 0;
stroker->dash_on = 1-stroker->dash_on;
stroker->dash_remain = stroker->style->dash[stroker->dash_index];
}
}
static void
_cairo_stroker_init (cairo_stroker_t *stroker,
cairo_stroke_style_t *stroke_style,
cairo_matrix_t *ctm,
cairo_matrix_t *ctm_inverse,
double tolerance,
cairo_traps_t *traps)
{
stroker->style = stroke_style;
stroker->ctm = ctm;
stroker->ctm_inverse = ctm_inverse;
stroker->tolerance = tolerance;
stroker->traps = traps;
_cairo_pen_init (&stroker->pen,
stroke_style->line_width / 2.0,
tolerance, ctm);
stroker->has_current_face = FALSE;
stroker->has_first_face = FALSE;
stroker->has_sub_path = FALSE;
if (stroker->style->dash)
_cairo_stroker_start_dash (stroker);
else
stroker->dashed = FALSE;
}
static void
_cairo_stroker_fini (cairo_stroker_t *stroker)
{
_cairo_pen_fini (&stroker->pen);
}
static void
_translate_point (cairo_point_t *point, cairo_point_t *offset)
{
point->x += offset->x;
point->y += offset->y;
}
static int
_cairo_stroker_face_clockwise (cairo_stroke_face_t *in, cairo_stroke_face_t *out)
{
cairo_slope_t in_slope, out_slope;
_cairo_slope_init (&in_slope, &in->point, &in->cw);
_cairo_slope_init (&out_slope, &out->point, &out->cw);
return _cairo_slope_clockwise (&in_slope, &out_slope);
}
static cairo_status_t
_cairo_stroker_join (cairo_stroker_t *stroker, cairo_stroke_face_t *in, cairo_stroke_face_t *out)
{
cairo_status_t status;
int clockwise = _cairo_stroker_face_clockwise (out, in);
cairo_point_t *inpt, *outpt;
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)
{
return CAIRO_STATUS_SUCCESS;
}
if (clockwise) {
inpt = &in->ccw;
outpt = &out->ccw;
} else {
inpt = &in->cw;
outpt = &out->cw;
}
switch (stroker->style->line_join) {
case CAIRO_LINE_JOIN_ROUND: {
int i;
int start, step, stop;
cairo_point_t tri[3];
cairo_pen_t *pen = &stroker->pen;
tri[0] = in->point;
if (clockwise) {
_cairo_pen_find_active_ccw_vertex_index (pen, &in->dev_vector, &start);
step = -1;
_cairo_pen_find_active_ccw_vertex_index (pen, &out->dev_vector, &stop);
} else {
_cairo_pen_find_active_cw_vertex_index (pen, &in->dev_vector, &start);
step = +1;
_cairo_pen_find_active_cw_vertex_index (pen, &out->dev_vector, &stop);
}
i = start;
tri[1] = *inpt;
while (i != stop) {
tri[2] = in->point;
_translate_point (&tri[2], &pen->vertices[i].point);
_cairo_traps_tessellate_triangle (stroker->traps, tri);
tri[1] = tri[2];
i += step;
if (i < 0)
i = pen->num_vertices - 1;
if (i >= pen->num_vertices)
i = 0;
}
tri[2] = *outpt;
return _cairo_traps_tessellate_triangle (stroker->traps, tri);
}
case CAIRO_LINE_JOIN_MITER:
default: {
/* dot product of incoming slope vector with outgoing slope vector */
double in_dot_out = ((-in->usr_vector.x * out->usr_vector.x)+
(-in->usr_vector.y * out->usr_vector.y));
double ml = stroker->style->miter_limit;
/*
* Check the miter limit -- lines meeting at an acute angle
* can generate long miters, the limit converts them to bevel
*
* We want to know when the miter is within the miter limit.
* That's straightforward to specify:
*
* secant (psi / 2) <= ml
*
* where psi is the angle between in and out
*
* secant(psi/2) = 1/sin(psi/2)
* 1/sin(psi/2) <= ml
* 1 <= ml sin(psi/2)
* 1 <= ml² sin²(psi/2)
* 2 <= ml² 2 sin²(psi/2)
* 2·sin²(psi/2) = 1-cos(psi)
* 2 <= ml² (1-cos(psi))
*
* in · out = |in| |out| cos (psi)
*
* in and out are both unit vectors, so:
*
* in · out = cos (psi)
*
* 2 <= ml² (1 - in · out)
*
*/
if (2 <= ml * ml * (1 - in_dot_out)) {
double x1, y1, x2, y2;
double mx, my;
double dx1, dx2, dy1, dy2;
cairo_polygon_t polygon;
cairo_point_t outer;
/*
* we've got the points already transformed to device
* space, but need to do some computation with them and
* also need to transform the slope from user space to
* device space
*/
/* outer point of incoming line face */
x1 = _cairo_fixed_to_double (inpt->x);
y1 = _cairo_fixed_to_double (inpt->y);
dx1 = in->usr_vector.x;
dy1 = in->usr_vector.y;
cairo_matrix_transform_distance (stroker->ctm, &dx1, &dy1);
/* outer point of outgoing line face */
x2 = _cairo_fixed_to_double (outpt->x);
y2 = _cairo_fixed_to_double (outpt->y);
dx2 = out->usr_vector.x;
dy2 = out->usr_vector.y;
cairo_matrix_transform_distance (stroker->ctm, &dx2, &dy2);
/*
* Compute the location of the outer corner of the miter.
* That's pretty easy -- just the intersection of the two
* outer edges. We've got slopes and points on each
* of those edges. Compute my directly, then compute
* mx by using the edge with the larger dy; that avoids
* dividing by values close to zero.
*/
my = (((x2 - x1) * dy1 * dy2 - y2 * dx2 * dy1 + y1 * dx1 * dy2) /
(dx1 * dy2 - dx2 * dy1));
if (fabs (dy1) >= fabs (dy2))
mx = (my - y1) * dx1 / dy1 + x1;
else
mx = (my - y2) * dx2 / dy2 + x2;
/*
* Draw the quadrilateral
*/
outer.x = _cairo_fixed_from_double (mx);
outer.y = _cairo_fixed_from_double (my);
_cairo_polygon_init (&polygon);
_cairo_polygon_move_to (&polygon, &in->point);
_cairo_polygon_line_to (&polygon, inpt);
_cairo_polygon_line_to (&polygon, &outer);
_cairo_polygon_line_to (&polygon, outpt);
_cairo_polygon_close (&polygon);
status = _cairo_traps_tessellate_polygon (stroker->traps,
&polygon,
CAIRO_FILL_RULE_WINDING);
_cairo_polygon_fini (&polygon);
return status;
}
/* fall through ... */
}
case CAIRO_LINE_JOIN_BEVEL: {
cairo_point_t tri[3];
tri[0] = in->point;
tri[1] = *inpt;
tri[2] = *outpt;
return _cairo_traps_tessellate_triangle (stroker->traps, tri);
}
}
}
static cairo_status_t
_cairo_stroker_add_cap (cairo_stroker_t *stroker, cairo_stroke_face_t *f)
{
cairo_status_t status;
if (stroker->style->line_cap == CAIRO_LINE_CAP_BUTT)
return CAIRO_STATUS_SUCCESS;
switch (stroker->style->line_cap) {
case CAIRO_LINE_CAP_ROUND: {
int i;
int start, stop;
cairo_slope_t slope;
cairo_point_t tri[3];
cairo_pen_t *pen = &stroker->pen;
slope = f->dev_vector;
_cairo_pen_find_active_cw_vertex_index (pen, &slope, &start);
slope.dx = -slope.dx;
slope.dy = -slope.dy;
_cairo_pen_find_active_cw_vertex_index (pen, &slope, &stop);
tri[0] = f->point;
tri[1] = f->cw;
for (i=start; i != stop; i = (i+1) % pen->num_vertices) {
tri[2] = f->point;
_translate_point (&tri[2], &pen->vertices[i].point);
_cairo_traps_tessellate_triangle (stroker->traps, tri);
tri[1] = tri[2];
}
tri[2] = f->ccw;
return _cairo_traps_tessellate_triangle (stroker->traps, tri);
}
case CAIRO_LINE_CAP_SQUARE: {
double dx, dy;
cairo_slope_t fvector;
cairo_point_t occw, ocw;
cairo_polygon_t polygon;
dx = f->usr_vector.x;
dy = f->usr_vector.y;
dx *= stroker->style->line_width / 2.0;
dy *= stroker->style->line_width / 2.0;
cairo_matrix_transform_distance (stroker->ctm, &dx, &dy);
fvector.dx = _cairo_fixed_from_double (dx);
fvector.dy = _cairo_fixed_from_double (dy);
occw.x = f->ccw.x + fvector.dx;
occw.y = f->ccw.y + fvector.dy;
ocw.x = f->cw.x + fvector.dx;
ocw.y = f->cw.y + fvector.dy;
_cairo_polygon_init (&polygon);
_cairo_polygon_move_to (&polygon, &f->cw);
_cairo_polygon_line_to (&polygon, &ocw);
_cairo_polygon_line_to (&polygon, &occw);
_cairo_polygon_line_to (&polygon, &f->ccw);
_cairo_polygon_close (&polygon);
status = _cairo_traps_tessellate_polygon (stroker->traps, &polygon, CAIRO_FILL_RULE_WINDING);
_cairo_polygon_fini (&polygon);
return status;
}
case CAIRO_LINE_CAP_BUTT:
default:
return CAIRO_STATUS_SUCCESS;
}
}
static cairo_status_t
_cairo_stroker_add_leading_cap (cairo_stroker_t *stroker,
cairo_stroke_face_t *face)
{
cairo_stroke_face_t reversed;
cairo_point_t t;
reversed = *face;
/* The initial cap needs an outward facing vector. Reverse everything */
reversed.usr_vector.x = -reversed.usr_vector.x;
reversed.usr_vector.y = -reversed.usr_vector.y;
reversed.dev_vector.dx = -reversed.dev_vector.dx;
reversed.dev_vector.dy = -reversed.dev_vector.dy;
t = reversed.cw;
reversed.cw = reversed.ccw;
reversed.ccw = t;
return _cairo_stroker_add_cap (stroker, &reversed);
}
static cairo_status_t
_cairo_stroker_add_trailing_cap (cairo_stroker_t *stroker,
cairo_stroke_face_t *face)
{
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);
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)
{
double mag, det;
double line_dx, line_dy;
double face_dx, face_dy;
cairo_point_double_t usr_vector;
cairo_point_t offset_ccw, offset_cw;
line_dx = _cairo_fixed_to_double (slope->dx);
line_dy = _cairo_fixed_to_double (slope->dy);
/* faces are normal in user space, not device space */
cairo_matrix_transform_distance (stroker->ctm_inverse, &line_dx, &line_dy);
mag = sqrt (line_dx * line_dx + line_dy * line_dy);
if (mag == 0) {
/* XXX: Can't compute other face points. Do we want a tag in the face for this case? */
return;
}
/* normalize to unit length */
line_dx /= mag;
line_dy /= mag;
usr_vector.x = line_dx;
usr_vector.y = line_dy;
/*
* rotate to get a line_width/2 vector along the face, note that
* the vector must be rotated the right direction in device space,
* but by 90° in user space. So, the rotation depends on
* whether the ctm reflects or not, and that can be determined
* by looking at the determinant of the matrix.
*/
_cairo_matrix_compute_determinant (stroker->ctm, &det);
if (det >= 0)
{
face_dx = - line_dy * (stroker->style->line_width / 2.0);
face_dy = line_dx * (stroker->style->line_width / 2.0);
}
else
{
face_dx = line_dy * (stroker->style->line_width / 2.0);
face_dy = - line_dx * (stroker->style->line_width / 2.0);
}
/* back to device space */
cairo_matrix_transform_distance (stroker->ctm, &face_dx, &face_dy);
offset_ccw.x = _cairo_fixed_from_double (face_dx);
offset_ccw.y = _cairo_fixed_from_double (face_dy);
offset_cw.x = -offset_ccw.x;
offset_cw.y = -offset_ccw.y;
face->ccw = *point;
_translate_point (&face->ccw, &offset_ccw);
face->point = *point;
face->cw = *point;
_translate_point (&face->cw, &offset_cw);
face->usr_vector.x = usr_vector.x;
face->usr_vector.y = usr_vector.y;
face->dev_vector = *slope;
}
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)
{
cairo_status_t status;
cairo_polygon_t polygon;
_compute_face (p1, slope, stroker, start);
/* XXX: This could be optimized slightly by not calling
_compute_face again but rather translating the relevant
fields from start. */
_compute_face (p2, slope, stroker, end);
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
conditions. But since that would be ugly, I'd prefer to add a
status flag to the polygon object that I could check only once
at then end of this sequence, (like we do with cairo_t
already). */
_cairo_polygon_init (&polygon);
_cairo_polygon_move_to (&polygon, &start->cw);
_cairo_polygon_line_to (&polygon, &start->ccw);
_cairo_polygon_line_to (&polygon, &end->ccw);
_cairo_polygon_line_to (&polygon, &end->cw);
_cairo_polygon_close (&polygon);
/* XXX: We can't use tessellate_rectangle as the matrix may have
skewed this into a non-rectangular shape. Perhaps it would be
worth checking the matrix for skew so that the common case
could use the faster tessellate_rectangle rather than
tessellate_polygon? */
status = _cairo_traps_tessellate_polygon (stroker->traps,
&polygon, CAIRO_FILL_RULE_WINDING);
_cairo_polygon_fini (&polygon);
return status;
}
static cairo_status_t
_cairo_stroker_move_to (void *closure, cairo_point_t *point)
{
cairo_status_t status;
cairo_stroker_t *stroker = closure;
status = _cairo_stroker_add_caps (stroker);
if (status)
return status;
stroker->first_point = *point;
stroker->current_point = *point;
stroker->has_first_face = FALSE;
stroker->has_current_face = FALSE;
stroker->has_sub_path = FALSE;
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
_cairo_stroker_move_to_dashed (void *closure, cairo_point_t *point)
{
/* reset the dash pattern for new sub paths */
cairo_stroker_t *stroker = closure;
_cairo_stroker_start_dash (stroker);
return _cairo_stroker_move_to (closure, point);
}
static cairo_status_t
_cairo_stroker_line_to (void *closure, cairo_point_t *point)
{
cairo_status_t status;
cairo_stroker_t *stroker = closure;
cairo_stroke_face_t start, end;
cairo_point_t *p1 = &stroker->current_point;
cairo_point_t *p2 = point;
cairo_slope_t slope;
stroker->has_sub_path = TRUE;
if (p1->x == p2->x && p1->y == p2->y)
return CAIRO_STATUS_SUCCESS;
_cairo_slope_init (&slope, p1, p2);
status = _cairo_stroker_add_sub_edge (stroker, p1, p2, &slope, &start, &end);
if (status)
return status;
if (stroker->has_current_face) {
status = _cairo_stroker_join (stroker, &stroker->current_face, &start);
if (status)
return status;
} else {
if (!stroker->has_first_face) {
stroker->first_face = start;
stroker->has_first_face = TRUE;
}
}
stroker->current_face = end;
stroker->has_current_face = TRUE;
stroker->current_point = *point;
return CAIRO_STATUS_SUCCESS;
}
/*
* Dashed lines. Cap each dash end, join around turns when on
*/
static cairo_status_t
_cairo_stroker_line_to_dashed (void *closure, cairo_point_t *point)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
cairo_stroker_t *stroker = closure;
double mag, remain, tmp;
double dx, dy;
double dx2, dy2;
cairo_point_t fd1, fd2;
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)
return CAIRO_STATUS_SUCCESS;
_cairo_slope_init (&slope, p1, p2);
dx = _cairo_fixed_to_double (p2->x - p1->x);
dy = _cairo_fixed_to_double (p2->y - p1->y);
cairo_matrix_transform_distance (stroker->ctm_inverse, &dx, &dy);
mag = sqrt (dx *dx + dy * dy);
remain = mag;
fd1 = *p1;
while (remain) {
tmp = stroker->dash_remain;
if (tmp > remain)
tmp = remain;
remain -= tmp;
dx2 = dx * (mag - remain)/mag;
dy2 = dy * (mag - remain)/mag;
cairo_matrix_transform_distance (stroker->ctm, &dx2, &dy2);
fd2.x = _cairo_fixed_from_double (dx2);
fd2.y = _cairo_fixed_from_double (dy2);
fd2.x += p1->x;
fd2.y += p1->y;
/*
* XXX simplify this case analysis
*/
if (stroker->dash_on) {
status = _cairo_stroker_add_sub_edge (stroker, &fd1, &fd2, &slope, &sub_start, &sub_end);
if (status)
return status;
if (!first) {
/*
* Not first dash in this segment, cap start
*/
status = _cairo_stroker_add_leading_cap (stroker, &sub_start);
if (status)
return status;
} else {
/*
* First in this segment, join to any current_face, else
* if at start of sub-path, mark position, else
* cap
*/
if (stroker->has_current_face) {
status = _cairo_stroker_join (stroker, &stroker->current_face, &sub_start);
if (status)
return status;
} else {
if (!stroker->has_first_face) {
stroker->first_face = sub_start;
stroker->has_first_face = TRUE;
} else {
status = _cairo_stroker_add_leading_cap (stroker, &sub_start);
if (status)
return status;
}
}
}
if (remain) {
/*
* Cap if not at end of segment
*/
status = _cairo_stroker_add_trailing_cap (stroker, &sub_end);
if (status)
return status;
} else {
/*
* Mark previous line face and fix up next time
* through
*/
stroker->current_face = sub_end;
stroker->has_current_face = TRUE;
}
} else {
/*
* If starting with off dash, check previous face
* and cap if necessary
*/
if (first) {
if (stroker->has_current_face) {
status = _cairo_stroker_add_trailing_cap (stroker, &stroker->current_face);
if (status)
return status;
}
}
if (!remain)
stroker->has_current_face = FALSE;
}
_cairo_stroker_step_dash (stroker, tmp);
fd1 = fd2;
first = FALSE;
}
stroker->current_point = *point;
return status;
}
static cairo_status_t
_cairo_stroker_curve_to (void *closure,
cairo_point_t *b,
cairo_point_t *c,
cairo_point_t *d)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
cairo_stroker_t *stroker = closure;
cairo_spline_t spline;
cairo_pen_t pen;
cairo_stroke_face_t start, end;
cairo_point_t extra_points[4];
cairo_point_t *a = &stroker->current_point;
status = _cairo_spline_init (&spline, a, b, c, d);
if (status == CAIRO_INT_STATUS_DEGENERATE)
return CAIRO_STATUS_SUCCESS;
status = _cairo_pen_init_copy (&pen, &stroker->pen);
if (status)
goto CLEANUP_SPLINE;
_compute_face (a, &spline.initial_slope, stroker, &start);
_compute_face (d, &spline.final_slope, stroker, &end);
if (stroker->has_current_face) {
status = _cairo_stroker_join (stroker, &stroker->current_face, &start);
if (status)
return status;
} else {
if (!stroker->has_first_face) {
stroker->first_face = start;
stroker->has_first_face = TRUE;
}
}
stroker->current_face = end;
stroker->has_current_face = TRUE;
extra_points[0] = start.cw;
extra_points[0].x -= start.point.x;
extra_points[0].y -= start.point.y;
extra_points[1] = start.ccw;
extra_points[1].x -= start.point.x;
extra_points[1].y -= start.point.y;
extra_points[2] = end.cw;
extra_points[2].x -= end.point.x;
extra_points[2].y -= end.point.y;
extra_points[3] = end.ccw;
extra_points[3].x -= end.point.x;
extra_points[3].y -= end.point.y;
status = _cairo_pen_add_points (&pen, extra_points, 4);
if (status)
goto CLEANUP_PEN;
status = _cairo_pen_stroke_spline (&pen, &spline, stroker->tolerance, stroker->traps);
if (status)
goto CLEANUP_PEN;
CLEANUP_PEN:
_cairo_pen_fini (&pen);
CLEANUP_SPLINE:
_cairo_spline_fini (&spline);
stroker->current_point = *d;
return status;
}
/* We're using two different algorithms here for dashed and un-dashed
* splines. The dashed alogorithm uses the existing line dashing
* code. It's linear in path length, but gets subtly wrong results for
* self-intersecting paths (an outstanding but for self-intersecting
* non-curved paths as well). The non-dashed algorithm tessellates a
* single polygon for the whole curve. It handles the
* self-intersecting problem, but it's (unsurprisingly) not O(n) and
* more significantly, it doesn't yet handle dashes.
*
* The only reason we're doing split algortihms here is to
* minimize the impact of fixing the splines-aren't-dashed bug for
* 1.0.2. Long-term the right answer is to rewrite the whole pile
* of stroking code so that the entire result is computed as a
* single polygon that is tessellated, (that is, stroking can be
* built on top of filling). That will solve the self-intersecting
* problem. It will also increase the importance of implementing
* an efficient and more robust tessellator.
*/
static cairo_status_t
_cairo_stroker_curve_to_dashed (void *closure,
cairo_point_t *b,
cairo_point_t *c,
cairo_point_t *d)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
cairo_stroker_t *stroker = closure;
cairo_spline_t spline;
cairo_point_t *a = &stroker->current_point;
cairo_line_join_t line_join_save;
int i;
status = _cairo_spline_init (&spline, a, b, c, d);
if (status == CAIRO_INT_STATUS_DEGENERATE)
return CAIRO_STATUS_SUCCESS;
/* If the line width is so small that the pen is reduced to a
single point, then we have nothing to do. */
if (stroker->pen.num_vertices <= 1)
goto CLEANUP_SPLINE;
/* Temporarily modify the stroker to use round joins to guarantee
* smooth stroked curves. */
line_join_save = stroker->style->line_join;
stroker->style->line_join = CAIRO_LINE_JOIN_ROUND;
status = _cairo_spline_decompose (&spline, stroker->tolerance);
if (status)
goto CLEANUP_GSTATE;
for (i = 1; i < spline.num_points; i++) {
if (stroker->dashed)
status = _cairo_stroker_line_to_dashed (stroker, &spline.points[i]);
else
status = _cairo_stroker_line_to (stroker, &spline.points[i]);
if (status)
break;
}
CLEANUP_GSTATE:
stroker->style->line_join = line_join_save;
CLEANUP_SPLINE:
_cairo_spline_fini (&spline);
return status;
}
static cairo_status_t
_cairo_stroker_close_path (void *closure)
{
cairo_status_t status;
cairo_stroker_t *stroker = closure;
if (stroker->dashed)
status = _cairo_stroker_line_to_dashed (stroker, &stroker->first_point);
else
status = _cairo_stroker_line_to (stroker, &stroker->first_point);
if (status)
return status;
if (stroker->has_first_face && stroker->has_current_face) {
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_sub_path = FALSE;
stroker->has_first_face = FALSE;
stroker->has_current_face = FALSE;
return CAIRO_STATUS_SUCCESS;
}
cairo_status_t
_cairo_path_fixed_stroke_to_traps (cairo_path_fixed_t *path,
cairo_stroke_style_t *stroke_style,
cairo_matrix_t *ctm,
cairo_matrix_t *ctm_inverse,
double tolerance,
cairo_traps_t *traps)
{
cairo_status_t status = CAIRO_STATUS_SUCCESS;
cairo_stroker_t stroker;
_cairo_stroker_init (&stroker, stroke_style,
ctm, ctm_inverse, tolerance,
traps);
if (stroker.style->dash)
status = _cairo_path_fixed_interpret (path,
CAIRO_DIRECTION_FORWARD,
_cairo_stroker_move_to_dashed,
_cairo_stroker_line_to_dashed,
_cairo_stroker_curve_to_dashed,
_cairo_stroker_close_path,
&stroker);
else
status = _cairo_path_fixed_interpret (path,
CAIRO_DIRECTION_FORWARD,
_cairo_stroker_move_to,
_cairo_stroker_line_to,
_cairo_stroker_curve_to,
_cairo_stroker_close_path,
&stroker);
if (status)
goto BAIL;
status = _cairo_stroker_add_caps (&stroker);
BAIL:
_cairo_stroker_fini (&stroker);
return status;
}