/* * Dashed lines. Cap each dash end, join around turns when on */ static cairo_status_t _cairo_stroker_line_to_dashed (void *closure, const cairo_point_t *p2) { cairo_stroker_t *stroker = closure; double mag, remain, step_length = 0; double slope_dx, slope_dy; double dx2, dy2; cairo_stroke_face_t sub_start, sub_end; cairo_point_t *p1 = &stroker->current_point; cairo_slope_t dev_slope; cairo_line_t segment; cairo_bool_t fully_in_bounds; cairo_status_t status; stroker->has_initial_sub_path = stroker->dash.dash_starts_on; if (p1->x == p2->x && p1->y == p2->y) return CAIRO_STATUS_SUCCESS; fully_in_bounds = TRUE; if (stroker->has_bounds && (! _cairo_box_contains_point (&stroker->bounds, p1) || ! _cairo_box_contains_point (&stroker->bounds, p2))) { fully_in_bounds = FALSE; } _cairo_slope_init (&dev_slope, p1, p2); slope_dx = _cairo_fixed_to_double (p2->x - p1->x); slope_dy = _cairo_fixed_to_double (p2->y - p1->y); if (! _compute_normalized_device_slope (&slope_dx, &slope_dy, stroker->ctm_inverse, &mag)) { return CAIRO_STATUS_SUCCESS; } remain = mag; segment.p1 = *p1; while (remain) { step_length = MIN (stroker->dash.dash_remain, remain); remain -= step_length; dx2 = slope_dx * (mag - remain); dy2 = slope_dy * (mag - remain); cairo_matrix_transform_distance (stroker->ctm, &dx2, &dy2); segment.p2.x = _cairo_fixed_from_double (dx2) + p1->x; segment.p2.y = _cairo_fixed_from_double (dy2) + p1->y; if (stroker->dash.dash_on && (fully_in_bounds || (! stroker->has_first_face && stroker->dash.dash_starts_on) || _cairo_box_intersects_line_segment (&stroker->bounds, &segment))) { status = _cairo_stroker_add_sub_edge (stroker, &segment.p1, &segment.p2, &dev_slope, slope_dx, slope_dy, &sub_start, &sub_end); if (unlikely (status)) return status; if (stroker->has_current_face) { /* Join with final face from previous segment */ status = _cairo_stroker_join (stroker, &stroker->current_face, &sub_start); if (unlikely (status)) return status; stroker->has_current_face = FALSE; } else if (! stroker->has_first_face && stroker->dash.dash_starts_on) { /* Save sub path's first face in case needed for closing join */ stroker->first_face = sub_start; stroker->has_first_face = TRUE; } else { /* Cap dash start if not connecting to a previous segment */ status = _cairo_stroker_add_leading_cap (stroker, &sub_start); if (unlikely (status)) return status; } if (remain) { /* Cap dash end if not at end of segment */ status = _cairo_stroker_add_trailing_cap (stroker, &sub_end); if (unlikely (status)) return status; } else { stroker->current_face = sub_end; stroker->has_current_face = TRUE; } } else { if (stroker->has_current_face) { /* Cap final face from previous segment */ status = _cairo_stroker_add_trailing_cap (stroker, &stroker->current_face); if (unlikely (status)) return status; stroker->has_current_face = FALSE; } } _cairo_stroker_dash_step (&stroker->dash, step_length); segment.p1 = segment.p2; } if (stroker->dash.dash_on && ! stroker->has_current_face) { /* This segment ends on a transition to dash_on, compute a new face * and add cap for the beginning of the next dash_on step. * * Note: this will create a degenerate cap if this is not the last line * in the path. Whether this behaviour is desirable or not is debatable. * On one side these degenerate caps can not be reproduced with regular * path stroking. * On the other hand, Acroread 7 also produces the degenerate caps. */ _compute_face (p2, &dev_slope, slope_dx, slope_dy, stroker, &stroker->current_face); status = _cairo_stroker_add_leading_cap (stroker, &stroker->current_face); if (unlikely (status)) return status; stroker->has_current_face = TRUE; } stroker->current_point = *p2; return CAIRO_STATUS_SUCCESS; }
static cairo_status_t _cairo_rectilinear_stroker_line_to_dashed (void *closure, const cairo_point_t *point) { cairo_rectilinear_stroker_t *stroker = closure; const cairo_point_t *a = &stroker->current_point; const cairo_point_t *b = point; cairo_bool_t fully_in_bounds; double sf, sign, remain; cairo_fixed_t mag; cairo_status_t status; cairo_line_t segment; cairo_bool_t dash_on = FALSE; unsigned is_horizontal; /* We don't draw anything for degenerate paths. */ if (a->x == b->x && a->y == b->y) return CAIRO_STATUS_SUCCESS; /* We only support horizontal or vertical elements. */ assert (a->x == b->x || a->y == b->y); fully_in_bounds = TRUE; if (stroker->has_bounds && (! _cairo_box_contains_point (&stroker->bounds, a) || ! _cairo_box_contains_point (&stroker->bounds, b))) { fully_in_bounds = FALSE; } is_horizontal = a->y == b->y; if (is_horizontal) { mag = b->x - a->x; sf = fabs (stroker->ctm->xx); } else { mag = b->y - a->y; sf = fabs (stroker->ctm->yy); } if (mag < 0) { remain = _cairo_fixed_to_double (-mag); sign = 1.; } else { remain = _cairo_fixed_to_double (mag); is_horizontal |= FORWARDS; sign = -1.; } segment.p2 = segment.p1 = *a; while (remain > 0.) { double step_length; step_length = MIN (sf * stroker->dash.dash_remain, remain); remain -= step_length; mag = _cairo_fixed_from_double (sign*remain); if (is_horizontal & 0x1) segment.p2.x = b->x + mag; else segment.p2.y = b->y + mag; if (stroker->dash.dash_on && (fully_in_bounds || _cairo_box_intersects_line_segment (&stroker->bounds, &segment))) { status = _cairo_rectilinear_stroker_add_segment (stroker, &segment.p1, &segment.p2, is_horizontal | (remain <= 0.) << 2); if (unlikely (status)) return status; dash_on = TRUE; } else { dash_on = FALSE; } _cairo_stroker_dash_step (&stroker->dash, step_length / sf); segment.p1 = segment.p2; } if (stroker->dash.dash_on && ! dash_on && (fully_in_bounds || _cairo_box_intersects_line_segment (&stroker->bounds, &segment))) { /* This segment ends on a transition to dash_on, compute a new face * and add cap for the beginning of the next dash_on step. */ status = _cairo_rectilinear_stroker_add_segment (stroker, &segment.p1, &segment.p1, is_horizontal | JOIN); if (unlikely (status)) return status; } stroker->current_point = *point; stroker->open_sub_path = TRUE; return CAIRO_STATUS_SUCCESS; }