mirror of
https://gitlab.freedesktop.org/cairo/cairo.git
synced 2026-01-18 07:40:27 +01:00
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:
parent
54c8e8ccfc
commit
ba406866be
8 changed files with 890 additions and 1135 deletions
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
658
src/cairo-path-stroke-boxes.c
Normal file
658
src/cairo-path-stroke-boxes.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
70
src/cairo-stroke-dash-private.h
Normal file
70
src/cairo-stroke-dash-private.h
Normal 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
96
src/cairo-stroke-dash.c
Normal 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);
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue