struct stroke_contour { /* Note that these are not strictly contours as they may intersect */
cairo_contour_t contour;
} cw, ccw;
cairo_uint64_t contour_tolerance;
cairo_polygon_t *polygon;
if (c > 0) return 1; if (c < 0) return -1; return 0;
}
/* * Construct a fan around the midpoint using the vertices from pen between * inpt and outpt.
*/ staticvoid
add_fan (struct stroker *stroker, const cairo_slope_t *in_vector, const cairo_slope_t *out_vector, const cairo_point_t *midpt,
cairo_bool_t clockwise, struct stroke_contour *c)
{
cairo_pen_t *pen = &stroker->pen; int start, stop;
if (stroker->has_bounds &&
! _cairo_box_contains_point (&stroker->bounds, midpt)) return;
assert (stroker->pen.num_vertices);
if (clockwise) {
_cairo_pen_find_active_cw_vertices (pen,
in_vector, out_vector,
&start, &stop); while (start != stop) {
cairo_point_t p = *midpt;
translate_point (&p, &pen->vertices[start].point);
contour_add_point (stroker, c, &p);
/* On the inside, the previous end-point is always * closer to the new face by definition.
*/
last = *_cairo_contour_last_point (&inner->contour);
d_last = distance_from_face (out, &last, negate);
_cairo_contour_remove_last_point (&inner->contour);
switch (stroker->style.line_join) { case CAIRO_LINE_JOIN_ROUND: if ((in->dev_slope.x * out->dev_slope.x +
in->dev_slope.y * out->dev_slope.y) < stroker->spline_cusp_tolerance)
{ /* construct a fan around the common midpoint */
add_fan (stroker,
&in->dev_vector, &out->dev_vector, &in->point,
clockwise, outer);
} /* else: bevel join */ break;
case CAIRO_LINE_JOIN_MITER: default: { /* dot product of incoming slope vector with outgoing slope vector */ double in_dot_out = in->dev_slope.x * out->dev_slope.x +
in->dev_slope.y * out->dev_slope.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 * * Consider the miter join formed when two line segments * meet at an angle psi: * * /.\ * /. .\ * /./ \.\ * /./psi\.\ * * We can zoom in on the right half of that to see: * * |\ * | \ psi/2 * | \ * | \ * | \ * | \ * miter \ * length \ * | \ * | .\ * | . \ * |. line \ * \ width \ * \ \ * * * The right triangle in that figure, (the line-width side is * shown faintly with three '.' characters), gives us the * following expression relating miter length, angle and line * width: * * 1 /sin (psi/2) = miter_length / line_width * * The right-hand side of this relationship is the same ratio * in which the miter limit (ml) is expressed. We want to know * when the miter length is within the miter limit. That is * when the following condition holds: * * 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; double ix, iy; double fdx1, fdy1, fdx2, fdy2; double mdx, mdy;
/* * 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->dev_slope.x;
dy1 = in->dev_slope.y;
/* outer point of outgoing line face */
x2 = _cairo_fixed_to_double (outpt->x);
y2 = _cairo_fixed_to_double (outpt->y);
dx2 = out->dev_slope.x;
dy2 = out->dev_slope.y;
/* * 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;
/* * When the two outer edges are nearly parallel, slight * perturbations in the position of the outer points of the lines * caused by representing them in fixed point form can cause the * intersection point of the miter to move a large amount. If * that moves the miter intersection from between the two faces, * then draw a bevel instead.
*/
ix = _cairo_fixed_to_double (in->point.x);
iy = _cairo_fixed_to_double (in->point.y);
/* slope of one face */
fdx1 = x1 - ix; fdy1 = y1 - iy;
/* slope of the other face */
fdx2 = x2 - ix; fdy2 = y2 - iy;
/* slope from the intersection to the miter point */
mdx = mx - ix; mdy = my - iy;
/* * Make sure the miter point line lies between the two * faces by comparing the slopes
*/ if (slope_compare_sgn (fdx1, fdy1, mdx, mdy) !=
slope_compare_sgn (fdx2, fdy2, mdx, mdy))
{
cairo_point_t p;
switch (stroker->style.line_join) { case CAIRO_LINE_JOIN_ROUND: if ((in->dev_slope.x * out->dev_slope.x +
in->dev_slope.y * out->dev_slope.y) < stroker->spline_cusp_tolerance)
{ /* construct a fan around the common midpoint */
add_fan (stroker,
&in->dev_vector, &out->dev_vector, &in->point,
clockwise, outer);
} /* else: bevel join */ break;
case CAIRO_LINE_JOIN_MITER: default: { /* dot product of incoming slope vector with outgoing slope vector */ double in_dot_out = in->dev_slope.x * out->dev_slope.x +
in->dev_slope.y * out->dev_slope.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 * * Consider the miter join formed when two line segments * meet at an angle psi: * * /.\ * /. .\ * /./ \.\ * /./psi\.\ * * We can zoom in on the right half of that to see: * * |\ * | \ psi/2 * | \ * | \ * | \ * | \ * miter \ * length \ * | \ * | .\ * | . \ * |. line \ * \ width \ * \ \ * * * The right triangle in that figure, (the line-width side is * shown faintly with three '.' characters), gives us the * following expression relating miter length, angle and line * width: * * 1 /sin (psi/2) = miter_length / line_width * * The right-hand side of this relationship is the same ratio * in which the miter limit (ml) is expressed. We want to know * when the miter length is within the miter limit. That is * when the following condition holds: * * 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; double ix, iy; double fdx1, fdy1, fdx2, fdy2; double mdx, mdy;
/* * 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->dev_slope.x;
dy1 = in->dev_slope.y;
/* outer point of outgoing line face */
x2 = _cairo_fixed_to_double (outpt->x);
y2 = _cairo_fixed_to_double (outpt->y);
dx2 = out->dev_slope.x;
dy2 = out->dev_slope.y;
/* * 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;
/* * When the two outer edges are nearly parallel, slight * perturbations in the position of the outer points of the lines * caused by representing them in fixed point form can cause the * intersection point of the miter to move a large amount. If * that moves the miter intersection from between the two faces, * then draw a bevel instead.
*/
ix = _cairo_fixed_to_double (in->point.x);
iy = _cairo_fixed_to_double (in->point.y);
/* slope of one face */
fdx1 = x1 - ix; fdy1 = y1 - iy;
/* slope of the other face */
fdx2 = x2 - ix; fdy2 = y2 - iy;
/* slope from the intersection to the miter point */
mdx = mx - ix; mdy = my - iy;
/* * Make sure the miter point line lies between the two * faces by comparing the slopes
*/ if (slope_compare_sgn (fdx1, fdy1, mdx, mdy) !=
slope_compare_sgn (fdx2, fdy2, mdx, mdy))
{
cairo_point_t p;
/* * 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.
*/ if (! _cairo_matrix_is_identity (stroker->ctm_inverse)) { /* Normalize the matrix! */
cairo_matrix_transform_distance (stroker->ctm_inverse,
&slope_dx, &slope_dy);
normalize_slope (&slope_dx, &slope_dy);
if (stroker->has_current_face) { int clockwise = join_is_clockwise (&stroker->current_face, &face); /* Join with final face from previous segment */
outer_join (stroker, &stroker->current_face, &face, clockwise);
inner_join (stroker, &stroker->current_face, &face, clockwise);
} else { if (! stroker->has_first_face) { /* Save sub path's first face in case needed for closing join */
stroker->first_face = face;
stroker->has_first_face = TRUE;
}
stroker->has_current_face = TRUE;
status = line_to (stroker, &stroker->first_point); if (unlikely (status)) return status;
if (stroker->has_first_face && stroker->has_current_face) { /* Join first and final faces of sub path */
outer_close (stroker, &stroker->current_face, &stroker->first_face);
inner_close (stroker, &stroker->current_face, &stroker->first_face); #if 0
*_cairo_contour_first_point (&stroker->ccw.contour) =
*_cairo_contour_last_point (&stroker->ccw.contour);
_cairo_contour_reset (&stroker->path);
} #endif
_cairo_contour_reset (&stroker->cw.contour);
_cairo_contour_reset (&stroker->ccw.contour);
} else { /* Cap the start and end of the sub path as needed */
add_caps (stroker);
}
stroker.has_bounds = polygon->num_limits; if (stroker.has_bounds) { /* Extend the bounds in each direction to account for the maximum area * we might generate trapezoids, to capture line segments that are * outside of the bounds but which might generate rendering that's * within bounds.
*/ double dx, dy;
cairo_fixed_t fdx, fdy; int i;
stroker.bounds = polygon->limits[0]; for (i = 1; i < polygon->num_limits; i++)
_cairo_box_add_box (&stroker.bounds, &polygon->limits[i]);
/* If `CAIRO_LINE_JOIN_ROUND` is selected and a joint's `arc height` * is greater than `tolerance` then two segments are joined with * round-join, otherwise bevel-join is used. * * (See https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/372#note_1698225 * for an illustration.) * * `Arc height` is the distance from the center of arc's chord to * the center of the arc. It is also the difference of arc's radius * and the "distance from a point where segments are joined to the * chord" (distance to the chord). Arc's radius is the half of a line * width and the "distance to the chord" is equal to "half of a line width" * times `cos(half the angle between segment vectors)`. So * * arc_height = w/2 - w/2 * cos(phi/2), * * where `w/2` is the "half of a line width". * * Using the double angle cosine formula we can express the `cos(phi/2)` * with just `cos(phi)` which is also the dot product of segments' * unit vectors. * * cos(phi/2) = sqrt ( (1 + cos(phi)) / 2 ); * cos(phi/2) is in [0; 1] range, cannot be negative; * * cos(phi) = a . b = (ax * bx + ay * by), * * where `a` and `b` are unit vectors of the segments to be joined. * * Since `arc height` should be greater than the `tolerance` to produce * a round-join we can write * * w/2 * (1 - cos(phi/2)) > tolerance; * 1 - tolerance / (w/2) > cos(phi/2); [!] * * which can be rewritten with the above double angle formula to * * cos(phi) < 2 * ( 1 - tolerance / (w/2) )^2 - 1, * * [!] Note that `w/2` is in [tolerance; +inf] range, since `cos(phi/2)` * cannot be negative. The left part of the above inequality is the * dot product and the right part is the `spline_cusp_tolerance`: * * (ax * bx + ay * by) < spline_cusp_tolerance. * * In the code below only the `spline_cusp_tolerance` is calculated. * The dot product is calculated later, in the condition expression * itself. "Half of a line width" must be scaled with CTM for tolerance * condition to be properly met. Also, since `arch height` cannot exceed * the "half of a line width" and since `cos(phi/2)` cannot be negative, * when `tolerance` is greater than the "half of a line width" the * bevel-join should be produced.
*/ double scaled_hlw = hypot(stroker.half_line_width * ctm->xx,
stroker.half_line_width * ctm->yx);
stroker.pen.num_vertices = 0; if (path->has_curve_to ||
style->line_join == CAIRO_LINE_JOIN_ROUND ||
style->line_cap == CAIRO_LINE_CAP_ROUND) {
status = _cairo_pen_init (&stroker.pen,
stroker.half_line_width,
tolerance, ctm); if (unlikely (status)) return status;
/* 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) return CAIRO_STATUS_SUCCESS;
}
status = _cairo_path_fixed_interpret (path,
move_to,
line_to,
curve_to,
close_path,
&stroker); /* Cap the start and end of the final sub path as needed */ if (likely (status == CAIRO_STATUS_SUCCESS))
add_caps (&stroker);
_cairo_contour_fini (&stroker.cw.contour);
_cairo_contour_fini (&stroker.ccw.contour); if (stroker.pen.num_vertices)
_cairo_pen_fini (&stroker.pen);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.