diff --git a/src/cairo-path-stroke.c b/src/cairo-path-stroke.c index 5c4f4fc7b..272045a87 100644 --- a/src/cairo-path-stroke.c +++ b/src/cairo-path-stroke.c @@ -51,6 +51,7 @@ typedef struct _cairo_stroker_dash { double dash_offset; const double *dashes; + double approximate_dashes[2]; unsigned int num_dashes; } cairo_stroker_dash_t; @@ -137,15 +138,25 @@ _cairo_stroker_dash_step (cairo_stroker_dash_t *dash, double step) static void _cairo_stroker_dash_init (cairo_stroker_dash_t *dash, - const cairo_stroke_style_t *style) + const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + double tolerance) { dash->dashed = style->dash != NULL; if (! dash->dashed) return; - dash->dashes = style->dash; - dash->num_dashes = style->num_dashes; - dash->dash_offset = style->dash_offset; + if (_cairo_stroke_style_dash_can_approximate (style, ctm, tolerance)) { + _cairo_stroke_style_dash_approximate (style, ctm, tolerance, + &dash->dash_offset, + dash->approximate_dashes, + &dash->num_dashes); + dash->dashes = dash->approximate_dashes; + } else { + dash->dashes = style->dash; + dash->num_dashes = style->num_dashes; + dash->dash_offset = style->dash_offset; + } _cairo_stroker_dash_start (dash); } @@ -179,7 +190,7 @@ _cairo_stroker_init (cairo_stroker_t *stroker, stroker->has_first_face = FALSE; stroker->has_initial_sub_path = FALSE; - _cairo_stroker_dash_init (&stroker->dash, stroke_style); + _cairo_stroker_dash_init (&stroker->dash, stroke_style, ctm, tolerance); stroker->add_external_edge = NULL; @@ -1490,7 +1501,8 @@ _cairo_rectilinear_stroker_init (cairo_rectilinear_stroker_t *stroker, stroker->segments_size = ARRAY_LENGTH (stroker->segments_embedded); stroker->num_segments = 0; - _cairo_stroker_dash_init (&stroker->dash, stroke_style); + /* Assume 2*EPSILON tolerance */ + _cairo_stroker_dash_init (&stroker->dash, stroke_style, ctm, _cairo_fixed_to_double (2 * CAIRO_FIXED_EPSILON)); stroker->has_bounds = FALSE; } diff --git a/src/cairo-stroke-style.c b/src/cairo-stroke-style.c index 38518838c..2d1cfe4b4 100644 --- a/src/cairo-stroke-style.c +++ b/src/cairo-stroke-style.c @@ -120,3 +120,122 @@ _cairo_stroke_style_max_distance_from_path (const cairo_stroke_style_t *style, *dx = style_expansion * hypot (ctm->xx, ctm->xy); *dy = style_expansion * hypot (ctm->yy, ctm->yx); } + +/* + * Computes the period of a dashed stroke style. + * Returns 0 for non-dashed styles. + */ +double +_cairo_stroke_style_dash_period (const cairo_stroke_style_t *style) +{ + double period; + unsigned int i; + + period = 0.0; + for (i = 0; i < style->num_dashes; i++) + period += style->dash[i]; + + if (style->num_dashes & 1) + period *= 2.0; + + return period; +} + +/* + * Coefficient of the linear approximation (minimizing square difference) + * of the surface covered by round caps + */ +#define ROUND_MINSQ_APPROXIMATION (9*M_PI/32) + +/* + * Computes the length of the "on" part of a dashed stroke style, + * taking into account also line caps. + * Returns 0 for non-dashed styles. + */ +double +_cairo_stroke_style_dash_stroked (const cairo_stroke_style_t *style) +{ + double stroked, cap_scale; + unsigned int i; + + switch (style->line_cap) { + default: ASSERT_NOT_REACHED; + case CAIRO_LINE_CAP_BUTT: cap_scale = 0.0; break; + case CAIRO_LINE_CAP_ROUND: cap_scale = ROUND_MINSQ_APPROXIMATION; break; + case CAIRO_LINE_CAP_SQUARE: cap_scale = 1.0; break; + } + + stroked = 0.0; + if (style->num_dashes & 1) { + /* Each dash element is used both as on and as off. The order in which they are summed is + * irrelevant, so sum the coverage of one dash element, taken both on and off at each iteration */ + for (i = 0; i < style->num_dashes; i++) + stroked += style->dash[i] + cap_scale * MIN (style->dash[i], style->line_width); + } else { + /* Even (0, 2, ...) dashes are on and simply counted for the coverage, odd dashes are off, thus + * their coverage is approximated based on the area covered by the caps of adjacent on dases. */ + for (i = 0; i < style->num_dashes; i+=2) + stroked += style->dash[i] + cap_scale * MIN (style->dash[i+1], style->line_width); + } + + return stroked; +} + +/* + * Verifies if _cairo_stroke_style_dash_approximate should be used to generate + * an approximation of the dash pattern in the specified style, when used for + * stroking a path with the given CTM and tolerance. + * Always FALSE for non-dashed styles. + */ +cairo_bool_t +_cairo_stroke_style_dash_can_approximate (const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + double tolerance) +{ + double period; + + if (! style->num_dashes) + return FALSE; + + period = _cairo_stroke_style_dash_period (style); + return _cairo_matrix_transformed_circle_major_axis (ctm, period) < tolerance; +} + +/* + * Create a 2-dashes approximation of a dashed style, by making the "on" and "off" + * parts respect the original ratio. + */ +void +_cairo_stroke_style_dash_approximate (const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + double tolerance, + double *dash_offset, + double *dashes, + unsigned int *num_dashes) +{ + double coverage, scale, offset; + cairo_bool_t on = TRUE; + unsigned int i = 0; + + coverage = _cairo_stroke_style_dash_stroked (style) / _cairo_stroke_style_dash_period (style); + coverage = MIN (coverage, 1.0); + scale = tolerance / _cairo_matrix_transformed_circle_major_axis (ctm, 1.0); + + /* 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. */ + offset = style->dash_offset; + while (offset > 0.0 && offset >= style->dash[i]) { + offset -= style->dash[i]; + on = !on; + if (++i == style->num_dashes) + i = 0; + } + + *num_dashes = 2; + + dashes[0] = scale * coverage; + dashes[1] = scale * (1.0 - coverage); + + *dash_offset = on ? 0.0 : dashes[0]; +} diff --git a/src/cairoint.h b/src/cairoint.h index 5912173ff..14a9491d0 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -1749,6 +1749,26 @@ _cairo_stroke_style_max_distance_from_path (const cairo_stroke_style_t *style, const cairo_matrix_t *ctm, double *dx, double *dy); +cairo_private double +_cairo_stroke_style_dash_period (const cairo_stroke_style_t *style); + +cairo_private double +_cairo_stroke_style_dash_stroked (const cairo_stroke_style_t *style); + +cairo_private cairo_bool_t +_cairo_stroke_style_dash_can_approximate (const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + double tolerance); + +cairo_private void +_cairo_stroke_style_dash_approximate (const cairo_stroke_style_t *style, + const cairo_matrix_t *ctm, + double tolerance, + double *dash_offset, + double *dashes, + unsigned int *num_dashes); + + /* cairo-surface.c */ cairo_private cairo_surface_t *