diff --git a/src/Makefile.sources b/src/Makefile.sources index 5b116c6bb..c007d18f8 100644 --- a/src/Makefile.sources +++ b/src/Makefile.sources @@ -96,6 +96,7 @@ cairo_private = \ cairo-scaled-font-private.h \ cairo-slope-private.h \ cairo-spans-private.h \ + cairo-stroke-dash-private.h \ cairo-surface-fallback-private.h \ cairo-surface-private.h \ cairo-surface-clipper-private.h \ @@ -160,6 +161,7 @@ cairo_sources = \ cairo-path-fixed.c \ cairo-path-in-fill.c \ cairo-path-stroke.c \ + cairo-path-stroke-boxes.c \ cairo-pattern.c \ cairo-pen.c \ cairo-polygon.c \ @@ -174,6 +176,7 @@ cairo_sources = \ cairo-slope.c \ cairo-spans.c \ cairo-spline.c \ + cairo-stroke-dash.c \ cairo-stroke-style.c \ cairo-surface.c \ cairo-surface-fallback.c \ diff --git a/src/cairo-path-fill.c b/src/cairo-path-fill.c index b15b1a4a0..ce4c86a45 100644 --- a/src/cairo-path-fill.c +++ b/src/cairo-path-fill.c @@ -235,251 +235,6 @@ _cairo_path_fixed_fill_to_traps (const cairo_path_fixed_t *path, return status; } -static cairo_region_t * -_cairo_path_fixed_fill_rectilinear_tessellate_to_region (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - const cairo_rectangle_int_t *extents) -{ - cairo_box_t box; - cairo_polygon_t polygon; - cairo_traps_t traps; - cairo_status_t status; - cairo_region_t *region; - - /* first try to bypass fill-to-polygon */ - _cairo_traps_init (&traps); - status = _cairo_path_fixed_fill_rectilinear_to_traps (path, - fill_rule, - CAIRO_ANTIALIAS_NONE, - &traps); - if (_cairo_status_is_error (status)) - goto CLEANUP_TRAPS; - - if (status == CAIRO_STATUS_SUCCESS) { - status = _cairo_traps_extract_region (&traps, - CAIRO_ANTIALIAS_NONE, - ®ion); - goto CLEANUP_TRAPS; - } - - /* path is not rectangular, try extracting clipped rectilinear edges */ - if (extents != NULL) { - _cairo_box_from_rectangle (&box, extents); - _cairo_polygon_init (&polygon, &box, 1); - } else { - _cairo_polygon_init (&polygon, NULL, 0); - } - - /* tolerance will be ignored as the path is rectilinear */ - status = _cairo_path_fixed_fill_to_polygon (path, 0., &polygon); - if (unlikely (status)) - goto CLEANUP_POLYGON; - - if (polygon.num_edges == 0) { - region = cairo_region_create (); - } else { - status = - _cairo_bentley_ottmann_tessellate_rectilinear_polygon (&traps, - &polygon, - fill_rule); - if (likely (status == CAIRO_STATUS_SUCCESS)) - status = _cairo_traps_extract_region (&traps, - CAIRO_ANTIALIAS_NONE, - ®ion); - } - - CLEANUP_POLYGON: - _cairo_polygon_fini (&polygon); - - CLEANUP_TRAPS: - _cairo_traps_fini (&traps); - - if (unlikely (status)) - region = _cairo_region_create_in_error (status); - - return region; -} - -/* This special-case filler supports only a path that describes a - * device-axis aligned rectangle. It exists to avoid the overhead of - * the general tessellator when drawing very common rectangles. - * - * If the path described anything but a device-axis aligned rectangle, - * this function will abort. - */ -cairo_region_t * -_cairo_path_fixed_fill_rectilinear_to_region (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - const cairo_rectangle_int_t *extents) -{ - cairo_rectangle_int_t rectangle_stack[CAIRO_STACK_ARRAY_LENGTH (cairo_rectangle_int_t)]; - cairo_box_t box; - cairo_region_t *region = NULL; - - assert (_cairo_path_fixed_fill_maybe_region (path)); - assert (! _cairo_path_fixed_fill_is_empty (path)); - - if (_cairo_path_fixed_is_box (path, &box)) { - rectangle_stack[0].x = _cairo_fixed_integer_part (box.p1.x); - rectangle_stack[0].y = _cairo_fixed_integer_part (box.p1.y); - rectangle_stack[0].width = _cairo_fixed_integer_part (box.p2.x) - - rectangle_stack[0].x; - rectangle_stack[0].height = _cairo_fixed_integer_part (box.p2.y) - - rectangle_stack[0].y; - if (! _cairo_rectangle_intersect (&rectangle_stack[0], extents)) - region = cairo_region_create (); - else - region = cairo_region_create_rectangle (&rectangle_stack[0]); - } else if (fill_rule == CAIRO_FILL_RULE_WINDING) { - cairo_rectangle_int_t *rects = rectangle_stack; - cairo_path_fixed_iter_t iter; - int last_cw = -1; - int size = ARRAY_LENGTH (rectangle_stack); - int count = 0; - - /* Support a series of rectangles as can be expected to describe a - * GdkRegion clip region during exposes. - */ - _cairo_path_fixed_iter_init (&iter, path); - while (_cairo_path_fixed_iter_is_fill_box (&iter, &box)) { - int cw = 0; - - if (box.p1.x > box.p2.x) { - cairo_fixed_t t; - - t = box.p1.x; - box.p1.x = box.p2.x; - box.p2.x = t; - - cw = ! cw; - } - - if (box.p1.y > box.p2.y) { - cairo_fixed_t t; - - t = box.p1.y; - box.p1.y = box.p2.y; - box.p2.y = t; - - cw = ! cw; - } - - if (last_cw < 0) - last_cw = cw; - else if (last_cw != cw) - goto TESSELLATE; - - if (count == size) { - cairo_rectangle_int_t *new_rects; - - size *= 4; - if (rects == rectangle_stack) { - new_rects = _cairo_malloc_ab (size, - sizeof (cairo_rectangle_int_t)); - if (unlikely (new_rects == NULL)) { - /* XXX _cairo_region_nil */ - break; - } - memcpy (new_rects, rects, sizeof (rectangle_stack)); - } else { - new_rects = _cairo_realloc_ab (rects, size, - sizeof (cairo_rectangle_int_t)); - if (unlikely (new_rects == NULL)) { - /* XXX _cairo_region_nil */ - break; - } - } - rects = new_rects; - } - - rects[count].x = _cairo_fixed_integer_part (box.p1.x); - rects[count].y = _cairo_fixed_integer_part (box.p1.y); - rects[count].width = _cairo_fixed_integer_part (box.p2.x) - rects[count].x; - rects[count].height = _cairo_fixed_integer_part (box.p2.y) - rects[count].y; - if (_cairo_rectangle_intersect (&rects[count], extents)) - count++; - } - - if (_cairo_path_fixed_iter_at_end (&iter)) - region = cairo_region_create_rectangles (rects, count); - -TESSELLATE: - if (rects != rectangle_stack) - free (rects); - } - - if (region == NULL) { - /* Hmm, complex polygon */ - region = _cairo_path_fixed_fill_rectilinear_tessellate_to_region (path, - fill_rule, - extents); - - - } - - return region; -} - -cairo_int_status_t -_cairo_path_fixed_fill_rectilinear_to_traps (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - cairo_antialias_t antialias, - cairo_traps_t *traps) -{ - cairo_box_t box; - cairo_status_t status; - - traps->is_rectilinear = TRUE; - traps->is_rectangular = TRUE; - - if (_cairo_path_fixed_is_box (path, &box)) { - if (antialias == CAIRO_ANTIALIAS_NONE) { - box.p1.x = _cairo_fixed_round_down (box.p1.x); - box.p1.y = _cairo_fixed_round_down (box.p1.y); - box.p2.x = _cairo_fixed_round_down (box.p2.x); - box.p2.y = _cairo_fixed_round_down (box.p2.y); - } - return _cairo_traps_tessellate_rectangle (traps, &box.p1, &box.p2); - } else { - cairo_path_fixed_iter_t iter; - - _cairo_path_fixed_iter_init (&iter, path); - while (_cairo_path_fixed_iter_is_fill_box (&iter, &box)) { - if (box.p1.y > box.p2.y) { - cairo_fixed_t t; - - t = box.p1.y; - box.p1.y = box.p2.y; - box.p2.y = t; - - t = box.p1.x; - box.p1.x = box.p2.x; - box.p2.x = t; - } - - if (antialias == CAIRO_ANTIALIAS_NONE) { - box.p1.x = _cairo_fixed_round_down (box.p1.x); - box.p1.y = _cairo_fixed_round_down (box.p1.y); - box.p2.x = _cairo_fixed_round_down (box.p2.x); - box.p2.y = _cairo_fixed_round_down (box.p2.y); - } - - status = _cairo_traps_tessellate_rectangle (traps, - &box.p1, &box.p2); - if (unlikely (status)) { - _cairo_traps_clear (traps); - return status; - } - } - - if (_cairo_path_fixed_iter_at_end (&iter)) - return _cairo_bentley_ottmann_tessellate_rectangular_traps (traps, fill_rule); - - _cairo_traps_clear (traps); - return CAIRO_INT_STATUS_UNSUPPORTED; - } -} - static cairo_status_t _cairo_path_fixed_fill_rectilinear_tessellate_to_boxes (const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, diff --git a/src/cairo-path-stroke-boxes.c b/src/cairo-path-stroke-boxes.c new file mode 100644 index 000000000..de3a628ae --- /dev/null +++ b/src/cairo-path-stroke-boxes.c @@ -0,0 +1,658 @@ +/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */ +/* 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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 + * Chris Wilson + */ + +#define _BSD_SOURCE /* for hypot() */ +#include "cairoint.h" + +#include "cairo-box-private.h" +#include "cairo-boxes-private.h" +#include "cairo-error-private.h" +#include "cairo-path-fixed-private.h" +#include "cairo-slope-private.h" +#include "cairo-stroke-dash-private.h" + +typedef struct _segment_t { + cairo_point_t p1, p2; + cairo_bool_t is_horizontal; + cairo_bool_t has_join; +} segment_t; + +typedef struct _cairo_rectilinear_stroker { + const cairo_stroke_style_t *stroke_style; + const cairo_matrix_t *ctm; + cairo_antialias_t antialias; + + cairo_fixed_t half_line_width; + cairo_boxes_t *boxes; + cairo_point_t current_point; + cairo_point_t first_point; + cairo_bool_t open_sub_path; + + cairo_stroker_dash_t dash; + + cairo_bool_t has_bounds; + cairo_box_t bounds; + + int num_segments; + int segments_size; + segment_t *segments; + segment_t segments_embedded[8]; /* common case is a single rectangle */ +} cairo_rectilinear_stroker_t; + +static void +_cairo_rectilinear_stroker_limit (cairo_rectilinear_stroker_t *stroker, + const cairo_box_t *boxes, + int num_boxes) +{ + stroker->has_bounds = TRUE; + _cairo_boxes_get_extents (boxes, num_boxes, &stroker->bounds); + + stroker->bounds.p1.x -= stroker->half_line_width; + stroker->bounds.p2.x += stroker->half_line_width; + + stroker->bounds.p1.y -= stroker->half_line_width; + stroker->bounds.p2.y += stroker->half_line_width; +} + +static cairo_bool_t +_cairo_rectilinear_stroker_init (cairo_rectilinear_stroker_t *stroker, + const cairo_stroke_style_t *stroke_style, + const cairo_matrix_t *ctm, + cairo_antialias_t antialias, + cairo_boxes_t *boxes) +{ + /* This special-case rectilinear stroker only supports + * miter-joined lines (not curves) and a translation-only matrix + * (though it could probably be extended to support a matrix with + * uniform, integer scaling). + * + * It also only supports horizontal and vertical line_to + * elements. But we don't catch that here, but instead return + * UNSUPPORTED from _cairo_rectilinear_stroker_line_to if any + * non-rectilinear line_to is encountered. + */ + if (stroke_style->line_join != CAIRO_LINE_JOIN_MITER) + return FALSE; + + /* If the miter limit turns right angles into bevels, then we + * can't use this optimization. Remember, the ratio is + * 1/sin(ɸ/2). So the cutoff is 1/sin(π/4.0) or ⎷2, + * which we round for safety. */ + if (stroke_style->miter_limit < M_SQRT2) + return FALSE; + + if (! (stroke_style->line_cap == CAIRO_LINE_CAP_BUTT || + stroke_style->line_cap == CAIRO_LINE_CAP_SQUARE)) + { + return FALSE; + } + + if (! _cairo_matrix_has_unity_scale (ctm)) + return FALSE; + + stroker->stroke_style = stroke_style; + stroker->ctm = ctm; + stroker->antialias = antialias; + + stroker->half_line_width = + _cairo_fixed_from_double (stroke_style->line_width / 2.0); + stroker->open_sub_path = FALSE; + stroker->segments = stroker->segments_embedded; + stroker->segments_size = ARRAY_LENGTH (stroker->segments_embedded); + stroker->num_segments = 0; + + _cairo_stroker_dash_init (&stroker->dash, stroke_style); + + stroker->has_bounds = FALSE; + + stroker->boxes = boxes; + + return TRUE; +} + +static void +_cairo_rectilinear_stroker_fini (cairo_rectilinear_stroker_t *stroker) +{ + if (stroker->segments != stroker->segments_embedded) + free (stroker->segments); +} + +static cairo_status_t +_cairo_rectilinear_stroker_add_segment (cairo_rectilinear_stroker_t *stroker, + const cairo_point_t *p1, + const cairo_point_t *p2, + cairo_bool_t is_horizontal, + cairo_bool_t has_join) +{ + if (CAIRO_INJECT_FAULT ()) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + if (stroker->num_segments == stroker->segments_size) { + int new_size = stroker->segments_size * 2; + segment_t *new_segments; + + if (stroker->segments == stroker->segments_embedded) { + new_segments = _cairo_malloc_ab (new_size, sizeof (segment_t)); + if (unlikely (new_segments == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + + memcpy (new_segments, stroker->segments, + stroker->num_segments * sizeof (segment_t)); + } else { + new_segments = _cairo_realloc_ab (stroker->segments, + new_size, sizeof (segment_t)); + if (unlikely (new_segments == NULL)) + return _cairo_error (CAIRO_STATUS_NO_MEMORY); + } + + stroker->segments_size = new_size; + stroker->segments = new_segments; + } + + stroker->segments[stroker->num_segments].p1 = *p1; + stroker->segments[stroker->num_segments].p2 = *p2; + stroker->segments[stroker->num_segments].has_join = has_join; + stroker->segments[stroker->num_segments].is_horizontal = is_horizontal; + stroker->num_segments++; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_rectilinear_stroker_emit_segments (cairo_rectilinear_stroker_t *stroker) +{ + cairo_line_cap_t line_cap = stroker->stroke_style->line_cap; + cairo_fixed_t half_line_width = stroker->half_line_width; + cairo_status_t status; + int i; + + /* For each segment we generate a single rectangle. + * This rectangle is based on a perpendicular extension (by half the + * line width) of the segment endpoints * after some adjustments of the + * endpoints to account for caps and joins. + */ + for (i = 0; i < stroker->num_segments; i++) { + cairo_bool_t lengthen_initial, lengthen_final; + cairo_point_t *a, *b; + cairo_box_t box; + + a = &stroker->segments[i].p1; + b = &stroker->segments[i].p2; + + /* We adjust the initial point of the segment to extend the + * rectangle to include the previous cap or join, (this + * adjustment applies to all segments except for the first + * segment of open, butt-capped paths). + * + * Overlapping segments will be eliminated by the tessellation. + * Ideally, we would not emit these self-intersections at all, + * but that is tricky with segments shorter than half_line_width. + */ + lengthen_initial = TRUE; + lengthen_final = TRUE; + if (stroker->open_sub_path && line_cap == CAIRO_LINE_CAP_BUTT) { + if (i == 0) + lengthen_initial = FALSE; + + if (i == stroker->num_segments - 1) + lengthen_final = FALSE; + } + + /* Perform the adjustments of the endpoints. */ + if (a->y == b->y) { + if (a->x < b->x) { + if (lengthen_initial) + a->x -= half_line_width; + if (lengthen_final) + b->x += half_line_width; + } else { + if (lengthen_initial) + a->x += half_line_width; + if (lengthen_final) + b->x -= half_line_width; + } + } else { + if (a->y < b->y) { + if (lengthen_initial) + a->y -= half_line_width; + if (lengthen_final) + b->y += half_line_width; + } else { + if (lengthen_initial) + a->y += half_line_width; + if (lengthen_final) + b->y -= half_line_width; + } + } + + /* Form the rectangle by expanding by half the line width in + * either perpendicular direction. */ + if (a->y == b->y) { + a->y -= half_line_width; + b->y += half_line_width; + } else { + a->x -= half_line_width; + b->x += half_line_width; + } + + if (a->x < b->x) { + box.p1.x = a->x; + box.p2.x = b->x; + } else { + box.p1.x = b->x; + box.p2.x = a->x; + } + if (a->y < b->y) { + box.p1.y = a->y; + box.p2.y = b->y; + } else { + box.p1.y = b->y; + box.p2.y = a->y; + } + + status = _cairo_boxes_add (stroker->boxes, stroker->antialias, &box); + if (unlikely (status)) + return status; + } + + stroker->num_segments = 0; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_rectilinear_stroker_emit_segments_dashed (cairo_rectilinear_stroker_t *stroker) +{ + cairo_status_t status; + cairo_line_cap_t line_cap = stroker->stroke_style->line_cap; + cairo_fixed_t half_line_width = stroker->half_line_width; + int i; + + for (i = 0; i < stroker->num_segments; i++) { + cairo_point_t *a, *b; + cairo_bool_t is_horizontal; + cairo_box_t box; + + a = &stroker->segments[i].p1; + b = &stroker->segments[i].p2; + + is_horizontal = stroker->segments[i].is_horizontal; + + /* Handle the joins for a potentially degenerate segment. */ + if (line_cap == CAIRO_LINE_CAP_BUTT && + stroker->segments[i].has_join && + (i != stroker->num_segments - 1 || + (! stroker->open_sub_path && stroker->dash.dash_starts_on))) + { + cairo_slope_t out_slope; + int j = (i + 1) % stroker->num_segments; + + box.p1 = stroker->segments[i].p1; + box.p2 = stroker->segments[i].p2; + _cairo_slope_init (&out_slope, + &stroker->segments[j].p1, + &stroker->segments[j].p2); + + if (is_horizontal) { + if (box.p1.x <= box.p2.x) { + box.p1.x = box.p2.x; + box.p2.x += half_line_width; + } else { + box.p1.x = box.p2.x - half_line_width; + } + if (out_slope.dy >= 0) + box.p1.y -= half_line_width; + if (out_slope.dy <= 0) + box.p2.y += half_line_width; + } else { + if (box.p1.y <= box.p2.y) { + box.p1.y = box.p2.y; + box.p2.y += half_line_width; + } else { + box.p1.y = box.p2.y - half_line_width; + } + if (out_slope.dx >= 0) + box.p1.x -= half_line_width; + if (out_slope.dx <= 0) + box.p2.x += half_line_width; + } + + status = _cairo_boxes_add (stroker->boxes, stroker->antialias, &box); + if (unlikely (status)) + return status; + } + + /* Perform the adjustments of the endpoints. */ + if (is_horizontal) { + if (line_cap == CAIRO_LINE_CAP_SQUARE) { + if (a->x <= b->x) { + a->x -= half_line_width; + b->x += half_line_width; + } else { + a->x += half_line_width; + b->x -= half_line_width; + } + } + + a->y += half_line_width; + b->y -= half_line_width; + } else { + if (line_cap == CAIRO_LINE_CAP_SQUARE) { + if (a->y <= b->y) { + a->y -= half_line_width; + b->y += half_line_width; + } else { + a->y += half_line_width; + b->y -= half_line_width; + } + } + + a->x += half_line_width; + b->x -= half_line_width; + } + + if (a->x == b->x && a->y == b->y) + continue; + + if (a->x < b->x) { + box.p1.x = a->x; + box.p2.x = b->x; + } else { + box.p1.x = b->x; + box.p2.x = a->x; + } + if (a->y < b->y) { + box.p1.y = a->y; + box.p2.y = b->y; + } else { + box.p1.y = b->y; + box.p2.y = a->y; + } + + status = _cairo_boxes_add (stroker->boxes, stroker->antialias, &box); + if (unlikely (status)) + return status; + } + + stroker->num_segments = 0; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_rectilinear_stroker_move_to (void *closure, + const cairo_point_t *point) +{ + cairo_rectilinear_stroker_t *stroker = closure; + cairo_status_t status; + + if (stroker->dash.dashed) + status = _cairo_rectilinear_stroker_emit_segments_dashed (stroker); + else + status = _cairo_rectilinear_stroker_emit_segments (stroker); + if (unlikely (status)) + return status; + + /* reset the dash pattern for new sub paths */ + _cairo_stroker_dash_start (&stroker->dash); + + stroker->current_point = *point; + stroker->first_point = *point; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_rectilinear_stroker_line_to (void *closure, + const cairo_point_t *b) +{ + cairo_rectilinear_stroker_t *stroker = closure; + cairo_point_t *a = &stroker->current_point; + cairo_status_t status; + + /* We only support horizontal or vertical elements. */ + assert (a->x == b->x || a->y == b->y); + + /* We don't draw anything for degenerate paths. */ + if (a->x == b->x && a->y == b->y) + return CAIRO_STATUS_SUCCESS; + + status = _cairo_rectilinear_stroker_add_segment (stroker, a, b, + a->y == b->y, + TRUE); + + stroker->current_point = *b; + stroker->open_sub_path = TRUE; + + return status; +} + +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 sign, remain; + cairo_fixed_t mag; + cairo_status_t status; + cairo_line_t segment; + cairo_bool_t dash_on = FALSE; + cairo_bool_t 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; + else + mag = b->y - a->y; + if (mag < 0) { + remain = _cairo_fixed_to_double (-mag); + sign = 1.; + } else { + remain = _cairo_fixed_to_double (mag); + sign = -1.; + } + + segment.p2 = segment.p1 = *a; + while (remain > 0.) { + double step_length; + + step_length = MIN (stroker->dash.dash_remain, remain); + remain -= step_length; + + mag = _cairo_fixed_from_double (sign*remain); + if (is_horizontal) + 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.); + if (unlikely (status)) + return status; + + dash_on = TRUE; + } + else + { + dash_on = FALSE; + } + + _cairo_stroker_dash_step (&stroker->dash, step_length); + 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, + TRUE); + if (unlikely (status)) + return status; + } + + stroker->current_point = *point; + stroker->open_sub_path = TRUE; + + return CAIRO_STATUS_SUCCESS; +} + +static cairo_status_t +_cairo_rectilinear_stroker_close_path (void *closure) +{ + cairo_rectilinear_stroker_t *stroker = closure; + cairo_status_t status; + + /* We don't draw anything for degenerate paths. */ + if (! stroker->open_sub_path) + return CAIRO_STATUS_SUCCESS; + + if (stroker->dash.dashed) { + status = _cairo_rectilinear_stroker_line_to_dashed (stroker, + &stroker->first_point); + } else { + status = _cairo_rectilinear_stroker_line_to (stroker, + &stroker->first_point); + } + if (unlikely (status)) + return status; + + stroker->open_sub_path = FALSE; + + if (stroker->dash.dashed) + status = _cairo_rectilinear_stroker_emit_segments_dashed (stroker); + else + status = _cairo_rectilinear_stroker_emit_segments (stroker); + if (unlikely (status)) + return status; + + return CAIRO_STATUS_SUCCESS; +} + +cairo_int_status_t +_cairo_path_fixed_stroke_rectilinear_to_boxes (const cairo_path_fixed_t *path, + const cairo_stroke_style_t *stroke_style, + const cairo_matrix_t *ctm, + cairo_antialias_t antialias, + cairo_boxes_t *boxes) +{ + cairo_rectilinear_stroker_t rectilinear_stroker; + cairo_int_status_t status; + + assert (_cairo_path_fixed_stroke_is_rectilinear (path)); + + if (! _cairo_rectilinear_stroker_init (&rectilinear_stroker, + stroke_style, ctm, antialias, + boxes)) + { + return CAIRO_INT_STATUS_UNSUPPORTED; + } + + if (boxes->num_limits) { + _cairo_rectilinear_stroker_limit (&rectilinear_stroker, + boxes->limits, + boxes->num_limits); + } + + status = _cairo_path_fixed_interpret (path, + _cairo_rectilinear_stroker_move_to, + rectilinear_stroker.dash.dashed ? + _cairo_rectilinear_stroker_line_to_dashed : + _cairo_rectilinear_stroker_line_to, + NULL, + _cairo_rectilinear_stroker_close_path, + &rectilinear_stroker); + if (unlikely (status)) + goto BAIL; + + if (rectilinear_stroker.dash.dashed) + status = _cairo_rectilinear_stroker_emit_segments_dashed (&rectilinear_stroker); + else + status = _cairo_rectilinear_stroker_emit_segments (&rectilinear_stroker); + if (unlikely (status)) + goto BAIL; + + /* As we incrementally tessellate, we do not eliminate self-intersections */ + status = _cairo_bentley_ottmann_tessellate_boxes (boxes, + CAIRO_FILL_RULE_WINDING, + boxes); + if (unlikely (status)) + goto BAIL; + + _cairo_rectilinear_stroker_fini (&rectilinear_stroker); + + return CAIRO_STATUS_SUCCESS; + +BAIL: + _cairo_rectilinear_stroker_fini (&rectilinear_stroker); + _cairo_boxes_clear (boxes); + return status; +} diff --git a/src/cairo-path-stroke.c b/src/cairo-path-stroke.c index 31ce1ac06..02f6ba28f 100644 --- a/src/cairo-path-stroke.c +++ b/src/cairo-path-stroke.c @@ -44,18 +44,7 @@ #include "cairo-error-private.h" #include "cairo-path-fixed-private.h" #include "cairo-slope-private.h" - -typedef struct _cairo_stroker_dash { - cairo_bool_t dashed; - unsigned int dash_index; - cairo_bool_t dash_on; - cairo_bool_t dash_starts_on; - double dash_remain; - - double dash_offset; - const double *dashes; - unsigned int num_dashes; -} cairo_stroker_dash_t; +#include "cairo-stroke-dash-private.h" typedef struct cairo_stroker { cairo_stroke_style_t style; @@ -98,61 +87,6 @@ typedef struct cairo_stroker { cairo_box_t bounds; } cairo_stroker_t; -static void -_cairo_stroker_dash_start (cairo_stroker_dash_t *dash) -{ - double offset; - cairo_bool_t on = TRUE; - unsigned int i = 0; - - if (! dash->dashed) - return; - - offset = dash->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 >= dash->dashes[i]) { - offset -= dash->dashes[i]; - on = !on; - if (++i == dash->num_dashes) - i = 0; - } - - dash->dash_index = i; - dash->dash_on = dash->dash_starts_on = on; - dash->dash_remain = dash->dashes[i] - offset; -} - -static void -_cairo_stroker_dash_step (cairo_stroker_dash_t *dash, double step) -{ - dash->dash_remain -= step; - if (dash->dash_remain <= 0.) { - if (++dash->dash_index == dash->num_dashes) - dash->dash_index = 0; - - dash->dash_on = ! dash->dash_on; - dash->dash_remain = dash->dashes[dash->dash_index]; - } -} - -static void -_cairo_stroker_dash_init (cairo_stroker_dash_t *dash, - const cairo_stroke_style_t *style) -{ - 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; - - _cairo_stroker_dash_start (dash); -} - static cairo_status_t _cairo_stroker_init (cairo_stroker_t *stroker, const cairo_stroke_style_t *stroke_style, @@ -1393,21 +1327,6 @@ _cairo_path_fixed_stroke_to_traps (const cairo_path_fixed_t *path, cairo_int_status_t status; cairo_polygon_t polygon; - /* Before we do anything else, we attempt the rectilinear - * stroker. It's careful to generate trapezoids that align to - * device-pixel boundaries when possible. Many backends can render - * those much faster than non-aligned trapezoids, (by using clip - * regions, etc.) */ - if (_cairo_path_fixed_stroke_is_rectilinear (path)) { - status = _cairo_path_fixed_stroke_rectilinear_to_traps (path, - stroke_style, - ctm, - CAIRO_ANTIALIAS_DEFAULT, - traps); - if (status != CAIRO_INT_STATUS_UNSUPPORTED) - return status; - } - _cairo_polygon_init (&polygon, traps->limits, traps->num_limits); status = _cairo_path_fixed_stroke_to_polygon (path, @@ -1431,732 +1350,3 @@ BAIL: return status; } - -typedef struct _segment_t { - cairo_point_t p1, p2; - cairo_bool_t is_horizontal; - cairo_bool_t has_join; -} segment_t; - -typedef struct _cairo_rectilinear_stroker { - const cairo_stroke_style_t *stroke_style; - const cairo_matrix_t *ctm; - cairo_antialias_t antialias; - - cairo_fixed_t half_line_width; - cairo_bool_t do_traps; - void *container; - cairo_point_t current_point; - cairo_point_t first_point; - cairo_bool_t open_sub_path; - - cairo_stroker_dash_t dash; - - cairo_bool_t has_bounds; - cairo_box_t bounds; - - int num_segments; - int segments_size; - segment_t *segments; - segment_t segments_embedded[8]; /* common case is a single rectangle */ -} cairo_rectilinear_stroker_t; - -static void -_cairo_rectilinear_stroker_limit (cairo_rectilinear_stroker_t *stroker, - const cairo_box_t *boxes, - int num_boxes) -{ - stroker->has_bounds = TRUE; - _cairo_boxes_get_extents (boxes, num_boxes, &stroker->bounds); - - stroker->bounds.p1.x -= stroker->half_line_width; - stroker->bounds.p2.x += stroker->half_line_width; - - stroker->bounds.p1.y -= stroker->half_line_width; - stroker->bounds.p2.y += stroker->half_line_width; -} - -static cairo_bool_t -_cairo_rectilinear_stroker_init (cairo_rectilinear_stroker_t *stroker, - const cairo_stroke_style_t *stroke_style, - const cairo_matrix_t *ctm, - cairo_antialias_t antialias, - cairo_bool_t do_traps, - void *container) -{ - /* This special-case rectilinear stroker only supports - * miter-joined lines (not curves) and a translation-only matrix - * (though it could probably be extended to support a matrix with - * uniform, integer scaling). - * - * It also only supports horizontal and vertical line_to - * elements. But we don't catch that here, but instead return - * UNSUPPORTED from _cairo_rectilinear_stroker_line_to if any - * non-rectilinear line_to is encountered. - */ - if (stroke_style->line_join != CAIRO_LINE_JOIN_MITER) - return FALSE; - - /* If the miter limit turns right angles into bevels, then we - * can't use this optimization. Remember, the ratio is - * 1/sin(ɸ/2). So the cutoff is 1/sin(π/4.0) or ⎷2, - * which we round for safety. */ - if (stroke_style->miter_limit < M_SQRT2) - return FALSE; - - if (! (stroke_style->line_cap == CAIRO_LINE_CAP_BUTT || - stroke_style->line_cap == CAIRO_LINE_CAP_SQUARE)) - { - return FALSE; - } - - if (! _cairo_matrix_has_unity_scale (ctm)) - return FALSE; - - stroker->stroke_style = stroke_style; - stroker->ctm = ctm; - stroker->antialias = antialias; - - stroker->half_line_width = - _cairo_fixed_from_double (stroke_style->line_width / 2.0); - stroker->open_sub_path = FALSE; - stroker->segments = stroker->segments_embedded; - stroker->segments_size = ARRAY_LENGTH (stroker->segments_embedded); - stroker->num_segments = 0; - - _cairo_stroker_dash_init (&stroker->dash, stroke_style); - - stroker->has_bounds = FALSE; - - stroker->do_traps = do_traps; - stroker->container = container; - - return TRUE; -} - -static void -_cairo_rectilinear_stroker_fini (cairo_rectilinear_stroker_t *stroker) -{ - if (stroker->segments != stroker->segments_embedded) - free (stroker->segments); -} - -static cairo_status_t -_cairo_rectilinear_stroker_add_segment (cairo_rectilinear_stroker_t *stroker, - const cairo_point_t *p1, - const cairo_point_t *p2, - cairo_bool_t is_horizontal, - cairo_bool_t has_join) -{ - if (CAIRO_INJECT_FAULT ()) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - - if (stroker->num_segments == stroker->segments_size) { - int new_size = stroker->segments_size * 2; - segment_t *new_segments; - - if (stroker->segments == stroker->segments_embedded) { - new_segments = _cairo_malloc_ab (new_size, sizeof (segment_t)); - if (unlikely (new_segments == NULL)) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - - memcpy (new_segments, stroker->segments, - stroker->num_segments * sizeof (segment_t)); - } else { - new_segments = _cairo_realloc_ab (stroker->segments, - new_size, sizeof (segment_t)); - if (unlikely (new_segments == NULL)) - return _cairo_error (CAIRO_STATUS_NO_MEMORY); - } - - stroker->segments_size = new_size; - stroker->segments = new_segments; - } - - stroker->segments[stroker->num_segments].p1 = *p1; - stroker->segments[stroker->num_segments].p2 = *p2; - stroker->segments[stroker->num_segments].has_join = has_join; - stroker->segments[stroker->num_segments].is_horizontal = is_horizontal; - stroker->num_segments++; - - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_rectilinear_stroker_emit_segments (cairo_rectilinear_stroker_t *stroker) -{ - cairo_status_t status; - cairo_line_cap_t line_cap = stroker->stroke_style->line_cap; - cairo_fixed_t half_line_width = stroker->half_line_width; - int i; - - for (i = 0; i < stroker->num_segments; i++) { - cairo_point_t *a, *b; - cairo_bool_t lengthen_initial, shorten_final, lengthen_final; - - a = &stroker->segments[i].p1; - b = &stroker->segments[i].p2; - - /* For each segment we generate a single rectangular - * trapezoid. This rectangle is based on a perpendicular - * extension (by half the line width) of the segment endpoints - * after some adjustments of the endpoints to account for caps - * and joins. - */ - - /* We adjust the initial point of the segment to extend the - * rectangle to include the previous cap or join, (this - * adjustment applies to all segments except for the first - * segment of open, butt-capped paths). - */ - lengthen_initial = TRUE; - if (i == 0 && stroker->open_sub_path && line_cap == CAIRO_LINE_CAP_BUTT) - lengthen_initial = FALSE; - - /* The adjustment of the final point is trickier. For all but - * the last segment we shorten the segment at the final - * endpoint to not overlap with the subsequent join. For the - * last segment we do the same shortening if the path is - * closed. If the path is open and butt-capped we do no - * adjustment, while if it's open and square-capped we do a - * lengthening adjustment instead to include the cap. - */ - shorten_final = TRUE; - lengthen_final = FALSE; - if (i == stroker->num_segments - 1 && stroker->open_sub_path) { - shorten_final = FALSE; - if (line_cap == CAIRO_LINE_CAP_SQUARE) - lengthen_final = TRUE; - } - - /* Perform the adjustments of the endpoints. */ - if (a->y == b->y) { - if (a->x < b->x) { - if (lengthen_initial) - a->x -= half_line_width; - if (shorten_final) - b->x -= half_line_width; - else if (lengthen_final) - b->x += half_line_width; - } else { - if (lengthen_initial) - a->x += half_line_width; - if (shorten_final) - b->x += half_line_width; - else if (lengthen_final) - b->x -= half_line_width; - } - - if (a->x > b->x) { - cairo_point_t *t; - - t = a; - a = b; - b = t; - } - } else { - if (a->y < b->y) { - if (lengthen_initial) - a->y -= half_line_width; - if (shorten_final) - b->y -= half_line_width; - else if (lengthen_final) - b->y += half_line_width; - } else { - if (lengthen_initial) - a->y += half_line_width; - if (shorten_final) - b->y += half_line_width; - else if (lengthen_final) - b->y -= half_line_width; - } - - if (a->y > b->y) { - cairo_point_t *t; - - t = a; - a = b; - b = t; - } - } - - /* Form the rectangle by expanding by half the line width in - * either perpendicular direction. */ - if (a->y == b->y) { - a->y -= half_line_width; - b->y += half_line_width; - } else { - a->x -= half_line_width; - b->x += half_line_width; - } - - if (stroker->do_traps) { - if (stroker->antialias == CAIRO_ANTIALIAS_NONE) { - a->x = _cairo_fixed_round_down (a->x); - a->y = _cairo_fixed_round_down (a->y); - b->x = _cairo_fixed_round_down (b->x); - b->y = _cairo_fixed_round_down (b->y); - } - status = _cairo_traps_tessellate_rectangle (stroker->container, a, b); - } else { - cairo_box_t box; - - box.p1 = *a; - box.p2 = *b; - - status = _cairo_boxes_add (stroker->container, stroker->antialias, &box); - } - if (unlikely (status)) - return status; - } - - stroker->num_segments = 0; - - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_rectilinear_stroker_emit_segments_dashed (cairo_rectilinear_stroker_t *stroker) -{ - cairo_status_t status; - cairo_line_cap_t line_cap = stroker->stroke_style->line_cap; - cairo_fixed_t half_line_width = stroker->half_line_width; - int i; - - for (i = 0; i < stroker->num_segments; i++) { - cairo_point_t *a, *b; - cairo_bool_t is_horizontal; - - a = &stroker->segments[i].p1; - b = &stroker->segments[i].p2; - - is_horizontal = stroker->segments[i].is_horizontal; - - /* Handle the joins for a potentially degenerate segment. */ - if (line_cap == CAIRO_LINE_CAP_BUTT && - stroker->segments[i].has_join && - (i != stroker->num_segments - 1 || - (! stroker->open_sub_path && stroker->dash.dash_starts_on))) - { - cairo_point_t p1 = stroker->segments[i].p1; - cairo_point_t p2 = stroker->segments[i].p2; - cairo_slope_t out_slope; - int j = (i + 1) % stroker->num_segments; - - _cairo_slope_init (&out_slope, - &stroker->segments[j].p1, - &stroker->segments[j].p2); - - if (is_horizontal) { - if (p1.x <= p2.x) { - p1.x = p2.x; - p2.x += half_line_width; - } else { - p1.x = p2.x - half_line_width; - } - if (out_slope.dy >= 0) - p1.y -= half_line_width; - if (out_slope.dy <= 0) - p2.y += half_line_width; - } else { - if (p1.y <= p2.y) { - p1.y = p2.y; - p2.y += half_line_width; - } else { - p1.y = p2.y - half_line_width; - } - if (out_slope.dx >= 0) - p1.x -= half_line_width; - if (out_slope.dx <= 0) - p2.x += half_line_width; - } - - if (stroker->do_traps) { - if (stroker->antialias == CAIRO_ANTIALIAS_NONE) { - p1.x = _cairo_fixed_round_down (p1.x); - p1.y = _cairo_fixed_round_down (p1.y); - p2.x = _cairo_fixed_round_down (p2.x); - p2.y = _cairo_fixed_round_down (p2.y); - } - status = _cairo_traps_tessellate_rectangle (stroker->container, &p1, &p2); - } else { - cairo_box_t box; - - box.p1 = p1; - box.p2 = p2; - - status = _cairo_boxes_add (stroker->container, stroker->antialias, &box); - } - if (unlikely (status)) - return status; - } - - /* Perform the adjustments of the endpoints. */ - if (is_horizontal) { - if (line_cap == CAIRO_LINE_CAP_SQUARE) { - if (a->x <= b->x) { - a->x -= half_line_width; - b->x += half_line_width; - } else { - a->x += half_line_width; - b->x -= half_line_width; - } - } - - if (a->x > b->x) { - cairo_point_t *t; - - t = a; - a = b; - b = t; - } - - a->y -= half_line_width; - b->y += half_line_width; - } else { - if (line_cap == CAIRO_LINE_CAP_SQUARE) { - if (a->y <= b->y) { - a->y -= half_line_width; - b->y += half_line_width; - } else { - a->y += half_line_width; - b->y -= half_line_width; - } - } - - if (a->y > b->y) { - cairo_point_t *t; - - t = a; - a = b; - b = t; - } - - a->x -= half_line_width; - b->x += half_line_width; - } - - if (a->x == b->x && a->y == b->y) - continue; - - if (stroker->do_traps) { - if (stroker->antialias == CAIRO_ANTIALIAS_NONE) { - a->x = _cairo_fixed_round_down (a->x); - a->y = _cairo_fixed_round_down (a->y); - b->x = _cairo_fixed_round_down (b->x); - b->y = _cairo_fixed_round_down (b->y); - } - status = _cairo_traps_tessellate_rectangle (stroker->container, a, b); - } else { - cairo_box_t box; - - box.p1 = *a; - box.p2 = *b; - - status = _cairo_boxes_add (stroker->container, stroker->antialias, &box); - } - if (unlikely (status)) - return status; - } - - stroker->num_segments = 0; - - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_rectilinear_stroker_move_to (void *closure, - const cairo_point_t *point) -{ - cairo_rectilinear_stroker_t *stroker = closure; - cairo_status_t status; - - if (stroker->dash.dashed) - status = _cairo_rectilinear_stroker_emit_segments_dashed (stroker); - else - status = _cairo_rectilinear_stroker_emit_segments (stroker); - if (unlikely (status)) - return status; - - /* reset the dash pattern for new sub paths */ - _cairo_stroker_dash_start (&stroker->dash); - - stroker->current_point = *point; - stroker->first_point = *point; - - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_rectilinear_stroker_line_to (void *closure, - const cairo_point_t *b) -{ - cairo_rectilinear_stroker_t *stroker = closure; - cairo_point_t *a = &stroker->current_point; - cairo_status_t status; - - /* We only support horizontal or vertical elements. */ - assert (a->x == b->x || a->y == b->y); - - /* We don't draw anything for degenerate paths. */ - if (a->x == b->x && a->y == b->y) - return CAIRO_STATUS_SUCCESS; - - status = _cairo_rectilinear_stroker_add_segment (stroker, a, b, - a->y == b->y, - TRUE); - - stroker->current_point = *b; - stroker->open_sub_path = TRUE; - - return status; -} - -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 sign, remain; - cairo_fixed_t mag; - cairo_status_t status; - cairo_line_t segment; - cairo_bool_t dash_on = FALSE; - cairo_bool_t 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; - else - mag = b->y - a->y; - if (mag < 0) { - remain = _cairo_fixed_to_double (-mag); - sign = 1.; - } else { - remain = _cairo_fixed_to_double (mag); - sign = -1.; - } - - segment.p2 = segment.p1 = *a; - while (remain > 0.) { - double step_length; - - step_length = MIN (stroker->dash.dash_remain, remain); - remain -= step_length; - - mag = _cairo_fixed_from_double (sign*remain); - if (is_horizontal) - 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.); - if (unlikely (status)) - return status; - - dash_on = TRUE; - } - else - { - dash_on = FALSE; - } - - _cairo_stroker_dash_step (&stroker->dash, step_length); - 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, - TRUE); - if (unlikely (status)) - return status; - } - - stroker->current_point = *point; - stroker->open_sub_path = TRUE; - - return CAIRO_STATUS_SUCCESS; -} - -static cairo_status_t -_cairo_rectilinear_stroker_close_path (void *closure) -{ - cairo_rectilinear_stroker_t *stroker = closure; - cairo_status_t status; - - /* We don't draw anything for degenerate paths. */ - if (! stroker->open_sub_path) - return CAIRO_STATUS_SUCCESS; - - if (stroker->dash.dashed) { - status = _cairo_rectilinear_stroker_line_to_dashed (stroker, - &stroker->first_point); - } else { - status = _cairo_rectilinear_stroker_line_to (stroker, - &stroker->first_point); - } - if (unlikely (status)) - return status; - - stroker->open_sub_path = FALSE; - - if (stroker->dash.dashed) - status = _cairo_rectilinear_stroker_emit_segments_dashed (stroker); - else - status = _cairo_rectilinear_stroker_emit_segments (stroker); - if (unlikely (status)) - return status; - - return CAIRO_STATUS_SUCCESS; -} - -cairo_int_status_t -_cairo_path_fixed_stroke_rectilinear_to_traps (const cairo_path_fixed_t *path, - const cairo_stroke_style_t *stroke_style, - const cairo_matrix_t *ctm, - cairo_antialias_t antialias, - cairo_traps_t *traps) -{ - cairo_rectilinear_stroker_t rectilinear_stroker; - cairo_int_status_t status; - - assert (_cairo_path_fixed_stroke_is_rectilinear (path)); - - if (! _cairo_rectilinear_stroker_init (&rectilinear_stroker, - stroke_style, ctm, antialias, - TRUE, traps)) - { - return CAIRO_INT_STATUS_UNSUPPORTED; - } - - if (traps->num_limits) { - _cairo_rectilinear_stroker_limit (&rectilinear_stroker, - traps->limits, - traps->num_limits); - } - - status = _cairo_path_fixed_interpret (path, - _cairo_rectilinear_stroker_move_to, - rectilinear_stroker.dash.dashed ? - _cairo_rectilinear_stroker_line_to_dashed : - _cairo_rectilinear_stroker_line_to, - NULL, - _cairo_rectilinear_stroker_close_path, - &rectilinear_stroker); - if (unlikely (status)) - goto BAIL; - - if (rectilinear_stroker.dash.dashed) - status = _cairo_rectilinear_stroker_emit_segments_dashed (&rectilinear_stroker); - else - status = _cairo_rectilinear_stroker_emit_segments (&rectilinear_stroker); - - traps->is_rectilinear = 1; - traps->is_rectangular = 1; - /* As we incrementally tessellate, we do not eliminate self-intersections */ - traps->has_intersections = traps->num_traps > 1; -BAIL: - _cairo_rectilinear_stroker_fini (&rectilinear_stroker); - - if (unlikely (status)) - _cairo_traps_clear (traps); - - return status; -} - -cairo_int_status_t -_cairo_path_fixed_stroke_rectilinear_to_boxes (const cairo_path_fixed_t *path, - const cairo_stroke_style_t *stroke_style, - const cairo_matrix_t *ctm, - cairo_antialias_t antialias, - cairo_boxes_t *boxes) -{ - cairo_rectilinear_stroker_t rectilinear_stroker; - cairo_int_status_t status; - - assert (_cairo_path_fixed_stroke_is_rectilinear (path)); - - if (! _cairo_rectilinear_stroker_init (&rectilinear_stroker, - stroke_style, ctm, antialias, - FALSE, boxes)) - { - return CAIRO_INT_STATUS_UNSUPPORTED; - } - - if (boxes->num_limits) { - _cairo_rectilinear_stroker_limit (&rectilinear_stroker, - boxes->limits, - boxes->num_limits); - } - - status = _cairo_path_fixed_interpret (path, - _cairo_rectilinear_stroker_move_to, - rectilinear_stroker.dash.dashed ? - _cairo_rectilinear_stroker_line_to_dashed : - _cairo_rectilinear_stroker_line_to, - NULL, - _cairo_rectilinear_stroker_close_path, - &rectilinear_stroker); - if (unlikely (status)) - goto BAIL; - - if (rectilinear_stroker.dash.dashed) - status = _cairo_rectilinear_stroker_emit_segments_dashed (&rectilinear_stroker); - else - status = _cairo_rectilinear_stroker_emit_segments (&rectilinear_stroker); - if (unlikely (status)) - goto BAIL; - - /* As we incrementally tessellate, we do not eliminate self-intersections */ - status = _cairo_bentley_ottmann_tessellate_boxes (boxes, - CAIRO_FILL_RULE_WINDING, - boxes); - if (unlikely (status)) - goto BAIL; - - _cairo_rectilinear_stroker_fini (&rectilinear_stroker); - - return CAIRO_STATUS_SUCCESS; - -BAIL: - _cairo_rectilinear_stroker_fini (&rectilinear_stroker); - _cairo_boxes_clear (boxes); - return status; -} diff --git a/src/cairo-stroke-dash-private.h b/src/cairo-stroke-dash-private.h new file mode 100644 index 000000000..75c000cd7 --- /dev/null +++ b/src/cairo-stroke-dash-private.h @@ -0,0 +1,70 @@ +/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */ +/* 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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 + * Chris Wilson + */ + +#ifndef CAIRO_STROKE_DASH_PRIVATE_H +#define CAIRO_STROKE_DASH_PRIVATE_H + +#include "cairoint.h" + +CAIRO_BEGIN_DECLS + +typedef struct _cairo_stroker_dash { + cairo_bool_t dashed; + unsigned int dash_index; + cairo_bool_t dash_on; + cairo_bool_t dash_starts_on; + double dash_remain; + + double dash_offset; + const double *dashes; + unsigned int num_dashes; +} cairo_stroker_dash_t; + +cairo_private void +_cairo_stroker_dash_init (cairo_stroker_dash_t *dash, + const cairo_stroke_style_t *style); + +cairo_private void +_cairo_stroker_dash_start (cairo_stroker_dash_t *dash); + +cairo_private void +_cairo_stroker_dash_step (cairo_stroker_dash_t *dash, double step); + +CAIRO_END_DECLS + +#endif /* CAIRO_STROKE_DASH_PRIVATE_H */ diff --git a/src/cairo-stroke-dash.c b/src/cairo-stroke-dash.c new file mode 100644 index 000000000..d581bdc53 --- /dev/null +++ b/src/cairo-stroke-dash.c @@ -0,0 +1,96 @@ +/* -*- Mode: c; tab-width: 8; c-basic-offset: 4; indent-tabs-mode: t; -*- */ +/* 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., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, 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 + * Chris Wilson + */ + +#include "cairoint.h" + +#include "cairo-stroke-dash-private.h" + +void +_cairo_stroker_dash_start (cairo_stroker_dash_t *dash) +{ + double offset; + cairo_bool_t on = TRUE; + unsigned int i = 0; + + if (! dash->dashed) + return; + + offset = dash->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 >= dash->dashes[i]) { + offset -= dash->dashes[i]; + on = !on; + if (++i == dash->num_dashes) + i = 0; + } + + dash->dash_index = i; + dash->dash_on = dash->dash_starts_on = on; + dash->dash_remain = dash->dashes[i] - offset; +} + +void +_cairo_stroker_dash_step (cairo_stroker_dash_t *dash, double step) +{ + dash->dash_remain -= step; + if (dash->dash_remain <= 0.) { + if (++dash->dash_index == dash->num_dashes) + dash->dash_index = 0; + + dash->dash_on = ! dash->dash_on; + dash->dash_remain = dash->dashes[dash->dash_index]; + } +} + +void +_cairo_stroker_dash_init (cairo_stroker_dash_t *dash, + const cairo_stroke_style_t *style) +{ + 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; + + _cairo_stroker_dash_start (dash); +} diff --git a/src/cairo-surface-fallback.c b/src/cairo-surface-fallback.c index b65b2bfe7..ec6946be5 100644 --- a/src/cairo-surface-fallback.c +++ b/src/cairo-surface-fallback.c @@ -1038,7 +1038,7 @@ _cairo_surface_fallback_stroke (cairo_surface_t *surface, cairo_traps_t traps; cairo_composite_rectangles_t extents; cairo_rectangle_int_t rect; - cairo_status_t status; + cairo_int_status_t status; if (!_cairo_surface_get_extents (surface, &rect)) ASSERT_NOT_REACHED; @@ -1053,44 +1053,47 @@ _cairo_surface_fallback_stroke (cairo_surface_t *surface, _cairo_polygon_init_with_clip (&polygon, extents.clip); _cairo_traps_init_with_clip (&traps, extents.clip); + status = CAIRO_INT_STATUS_UNSUPPORTED; if (_cairo_path_fixed_stroke_is_rectilinear (path)) { - status = _cairo_path_fixed_stroke_rectilinear_to_traps (path, + cairo_boxes_t boxes; + + /* Did I mention, that I need to rewrite surface-fallback? */ + _cairo_boxes_init_with_clip (&boxes, extents.clip); + status = _cairo_path_fixed_stroke_rectilinear_to_boxes (path, stroke_style, ctm, antialias, - &traps); - if (likely (status == CAIRO_STATUS_SUCCESS)) - goto DO_TRAPS; + &boxes); + if (status == CAIRO_INT_STATUS_SUCCESS) + status = _cairo_traps_init_boxes (&traps, &boxes); + _cairo_boxes_fini (&boxes); - if (_cairo_status_is_error (status)) - goto CLEANUP; - } - - status = _cairo_path_fixed_stroke_to_polygon (path, - stroke_style, - ctm, ctm_inverse, - tolerance, - &polygon); - if (unlikely (status)) - goto CLEANUP; - - if (polygon.num_edges == 0) - goto DO_TRAPS; - - if (_cairo_operator_bounded_by_mask (op)) { - _cairo_box_round_to_rectangle (&polygon.extents, &extents.mask); - if (! _cairo_rectangle_intersect (&extents.bounded, &extents.mask)) + if (unlikely (_cairo_int_status_is_error (status))) goto CLEANUP; } /* Fall back to trapezoid fills. */ - status = _cairo_bentley_ottmann_tessellate_polygon (&traps, - &polygon, - CAIRO_FILL_RULE_WINDING); - if (unlikely (status)) - goto CLEANUP; + if (status == CAIRO_INT_STATUS_UNSUPPORTED) { + status = _cairo_path_fixed_stroke_to_polygon (path, + stroke_style, + ctm, ctm_inverse, + tolerance, + &polygon); + if (unlikely (status)) + goto CLEANUP; + + status = _cairo_composite_rectangles_intersect_mask_extents (&extents, + &polygon.extents); + if (unlikely (status)) + goto CLEANUP; + + status = _cairo_bentley_ottmann_tessellate_polygon (&traps, + &polygon, + CAIRO_FILL_RULE_WINDING); + if (unlikely (status)) + goto CLEANUP; + } - DO_TRAPS: status = _clip_and_composite_trapezoids (source, op, surface, &traps, antialias, extents.clip, @@ -1116,10 +1119,9 @@ _cairo_surface_fallback_fill (cairo_surface_t *surface, { cairo_polygon_t polygon; cairo_traps_t traps; - cairo_bool_t is_rectilinear; cairo_composite_rectangles_t extents; cairo_rectangle_int_t rect; - cairo_status_t status; + cairo_int_status_t status; if (!_cairo_surface_get_extents (surface, &rect)) ASSERT_NOT_REACHED; @@ -1136,49 +1138,43 @@ _cairo_surface_fallback_fill (cairo_surface_t *surface, if (_cairo_path_fixed_fill_is_empty (path)) goto DO_TRAPS; - is_rectilinear = _cairo_path_fixed_fill_is_rectilinear (path); - if (is_rectilinear) { - status = _cairo_path_fixed_fill_rectilinear_to_traps (path, + status = CAIRO_INT_STATUS_UNSUPPORTED; + if (_cairo_path_fixed_fill_is_rectilinear (path)) { + cairo_boxes_t boxes; + + /* meh, surface-fallback is dying anyway... */ + _cairo_boxes_init_with_clip (&boxes, extents.clip); + status = _cairo_path_fixed_fill_rectilinear_to_boxes (path, fill_rule, antialias, - &traps); - if (likely (status == CAIRO_STATUS_SUCCESS)) - goto DO_TRAPS; - - if (_cairo_status_is_error (status)) + &boxes); + if (unlikely (status)) goto CLEANUP; - } - status = _cairo_path_fixed_fill_to_polygon (path, tolerance, &polygon); - if (unlikely (status)) - goto CLEANUP; - - if (polygon.num_edges == 0) - goto DO_TRAPS; - - if (_cairo_operator_bounded_by_mask (op)) { - _cairo_box_round_to_rectangle (&polygon.extents, &extents.mask); - if (! _cairo_rectangle_intersect (&extents.bounded, &extents.mask)) - goto CLEANUP; - } - - if (is_rectilinear) { - status = _cairo_bentley_ottmann_tessellate_rectilinear_polygon (&traps, - &polygon, - fill_rule); - if (likely (status == CAIRO_STATUS_SUCCESS)) - goto DO_TRAPS; - - if (unlikely (_cairo_status_is_error (status))) + if (status == CAIRO_INT_STATUS_SUCCESS) + status = _cairo_traps_init_boxes (&traps, &boxes); + _cairo_boxes_fini (&boxes); + if (unlikely (_cairo_int_status_is_error (status))) goto CLEANUP; } /* Fall back to trapezoid fills. */ - status = _cairo_bentley_ottmann_tessellate_polygon (&traps, - &polygon, - fill_rule); - if (unlikely (status)) - goto CLEANUP; + if (status == CAIRO_INT_STATUS_UNSUPPORTED) { + status = _cairo_path_fixed_fill_to_polygon (path, tolerance, &polygon); + if (unlikely (status)) + goto CLEANUP; + + status = _cairo_composite_rectangles_intersect_mask_extents (&extents, + &polygon.extents); + if (unlikely (status)) + goto CLEANUP; + + status = _cairo_bentley_ottmann_tessellate_polygon (&traps, + &polygon, + fill_rule); + if (unlikely (status)) + goto CLEANUP; + } DO_TRAPS: status = _clip_and_composite_trapezoids (source, op, surface, diff --git a/src/cairoint.h b/src/cairoint.h index 8d87aa5de..9a765570a 100644 --- a/src/cairoint.h +++ b/src/cairoint.h @@ -1371,12 +1371,6 @@ _cairo_path_fixed_fill_rectilinear_to_polygon (const cairo_path_fixed_t *path, cairo_antialias_t antialias, cairo_polygon_t *polygon); -cairo_private cairo_int_status_t -_cairo_path_fixed_fill_rectilinear_to_traps (const cairo_path_fixed_t *path, - cairo_fill_rule_t fill_rule, - cairo_antialias_t antialias, - cairo_traps_t *traps); - cairo_private cairo_status_t _cairo_path_fixed_fill_rectilinear_to_boxes (const cairo_path_fixed_t *path, cairo_fill_rule_t fill_rule, @@ -1403,13 +1397,6 @@ _cairo_path_fixed_stroke_to_polygon (const cairo_path_fixed_t *path, double tolerance, cairo_polygon_t *polygon); -cairo_private cairo_int_status_t -_cairo_path_fixed_stroke_rectilinear_to_traps (const cairo_path_fixed_t *path, - const cairo_stroke_style_t *stroke_style, - const cairo_matrix_t *ctm, - cairo_antialias_t antialias, - cairo_traps_t *traps); - cairo_private cairo_int_status_t _cairo_path_fixed_stroke_rectilinear_to_boxes (const cairo_path_fixed_t *path, const cairo_stroke_style_t *stroke_style,