stroke: Rely on the tessellator to remove self-intersections

As handling joins/caps between line segments shorter than
half_line_width is tricky.

Rather than also fixing the bug in traps, remove that code. The plan is
to avoiding hitting the traps code, short-circuiting several steps along
the fast rectangular paths.

Fixes line-width-overlap.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
This commit is contained in:
Chris Wilson 2011-08-13 15:20:03 +01:00
parent 54c8e8ccfc
commit ba406866be
8 changed files with 890 additions and 1135 deletions

View file

@ -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 \

View file

@ -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,
&region);
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,
&region);
}
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,

View file

@ -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 <cworth@cworth.org>
* Chris Wilson <chris@chris-wilson.co.uk>
*/
#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;
}

View file

@ -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;
}

View file

@ -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 <cworth@cworth.org>
* Chris Wilson <chris@chris-wilson.co.uk>
*/
#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 */

96
src/cairo-stroke-dash.c Normal file
View file

@ -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 <cworth@cworth.org>
* Chris Wilson <chris@chris-wilson.co.uk>
*/
#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);
}

View file

@ -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,

View file

@ -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,