cairo/xrpen.c

390 lines
9.9 KiB
C

/*
* $XFree86: $
*
* Copyright © 2002 Carl D. Worth
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of Carl
* D. Worth not be used in advertising or publicity pertaining to
* distribution of the software without specific, written prior
* permission. Carl D. Worth makes no representations about the
* suitability of this software for any purpose. It is provided "as
* is" without express or implied warranty.
*
* CARL D. WORTH DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
* SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS, IN NO EVENT SHALL CARL D. WORTH BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
* IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "xrint.h"
static int
_XrPenVerticesNeeded(double radius, double tolerance, XrTransform *matrix);
static void
_XrPenComputeSlopes(XrPen *pen);
static int
_SlopeClockwise(XrSlopeFixed *a, XrSlopeFixed *b);
static int
_SlopeCounterClockwise(XrSlopeFixed *a, XrSlopeFixed *b);
static int
_XrPenVertexCompareByTheta(const void *a, const void *b);
static XrStatus
_XrPenStrokeSplineHalf(XrPen *pen, XrSpline *spline, XrPenStrokeDirection dir, XrPolygon *polygon);
XrStatus
_XrPenInitEmpty(XrPen *pen)
{
pen->radius = 0;
pen->tolerance = 0;
pen->vertex = NULL;
pen->num_vertices = 0;
return XrStatusSuccess;
}
XrStatus
_XrPenInit(XrPen *pen, double radius, XrGState *gstate)
{
int i;
XrPenVertex *v;
double dx, dy;
if (pen->num_vertices) {
/* XXX: It would be nice to notice that the pen is already properly constructed.
However, this test would also have to account for possible changes in the transformation
matrix.
if (pen->radius == radius && pen->tolerance == tolerance)
return XrStatusSuccess;
*/
_XrPenDeinit(pen);
}
pen->radius = radius;
pen->tolerance = gstate->tolerance;
pen->num_vertices = _XrPenVerticesNeeded(radius, gstate->tolerance, &gstate->ctm);
/* number of vertices must be even */
if (pen->num_vertices % 2)
pen->num_vertices++;
pen->vertex = malloc(pen->num_vertices * sizeof(XrPenVertex));
if (pen->vertex == NULL) {
return XrStatusNoMemory;
}
for (i=0; i < pen->num_vertices; i++) {
v = &pen->vertex[i];
v->theta = 2 * M_PI * i / (double) pen->num_vertices;
dx = radius * cos(v->theta);
dy = radius * sin(v->theta);
_XrTransformDistance(&gstate->ctm, &dx, &dy);
v->pt.x = XDoubleToFixed(dx);
v->pt.y = XDoubleToFixed(dy);
/* Recompute theta in device space */
v->theta = atan2(v->pt.y, v->pt.x);
if (v->theta < 0)
v->theta += 2 * M_PI;
}
_XrPenComputeSlopes(pen);
return XrStatusSuccess;
}
void
_XrPenDeinit(XrPen *pen)
{
free(pen->vertex);
_XrPenInitEmpty(pen);
}
XrStatus
_XrPenInitCopy(XrPen *pen, XrPen *other)
{
*pen = *other;
if (pen->num_vertices) {
pen->vertex = malloc(pen->num_vertices * sizeof(XrPenVertex));
if (pen->vertex == NULL) {
return XrStatusNoMemory;
}
memcpy(pen->vertex, other->vertex, pen->num_vertices * sizeof(XrPenVertex));
}
return XrStatusSuccess;
}
static int
_XrPenVertexCompareByTheta(const void *a, const void *b)
{
double diff;
const XrPenVertex *va = a;
const XrPenVertex *vb = b;
diff = va->theta - vb->theta;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
else
return 0;
}
XrStatus
_XrPenAddPoints(XrPen *pen, XPointFixed *pt, int num_pts)
{
int i, j;
XrPenVertex *v, *v_next, *new_vertex;
pen->num_vertices += num_pts;
new_vertex = realloc(pen->vertex, pen->num_vertices * sizeof(XrPenVertex));
if (new_vertex == NULL) {
pen->num_vertices -= num_pts;
return XrStatusNoMemory;
}
pen->vertex = new_vertex;
/* initialize new vertices */
for (i=0; i < num_pts; i++) {
v = &pen->vertex[pen->num_vertices-(i+1)];
v->pt = pt[i];
v->theta = atan2(v->pt.y, v->pt.x);
if (v->theta < 0)
v->theta += 2 * M_PI;
}
qsort(pen->vertex, pen->num_vertices, sizeof(XrPenVertex), _XrPenVertexCompareByTheta);
/* eliminate any duplicate vertices */
for (i=0; i < pen->num_vertices - 1; i++ ) {
v = &pen->vertex[i];
v_next = &pen->vertex[i+1];
if (v->pt.x == v_next->pt.x && v->pt.y == v_next->pt.y) {
for (j=i+1; j < pen->num_vertices - 1; j++)
pen->vertex[j] = pen->vertex[j+1];
pen->num_vertices--;
/* There may be more of the same duplicate, check again */
i--;
}
}
_XrPenComputeSlopes(pen);
return XrStatusSuccess;
}
static int
_XrPenVerticesNeeded(double radius, double tolerance, XrTransform *matrix)
{
double e1, e2, emax, theta;
_XrTransformEigenValues(matrix, &e1, &e2);
if (fabs(e1) > fabs(e2))
emax = fabs(e1);
else
emax = fabs(e2);
if (tolerance > emax*radius) {
return 4;
}
theta = acos(1 - tolerance/(emax * radius));
return ceil(M_PI / theta);
}
static void
_XrPenComputeSlopes(XrPen *pen)
{
int i, i_prev;
XrPenVertex *prev, *v, *next;
for (i=0, i_prev = pen->num_vertices - 1;
i < pen->num_vertices;
i_prev = i++) {
prev = &pen->vertex[i_prev];
v = &pen->vertex[i];
next = &pen->vertex[(i + 1) % pen->num_vertices];
_ComputeSlope(&prev->pt, &v->pt, &v->slope_cw);
_ComputeSlope(&v->pt, &next->pt, &v->slope_ccw);
}
}
/* Is a clockwise of b?
*
* NOTE: The strict equality here is not significant in and of itself,
* but there are functions up above that are sensitive to it,
* (cf. _XrPenFindActiveCWVertexIndex).
*/
static int
_SlopeClockwise(XrSlopeFixed *a, XrSlopeFixed *b)
{
double a_dx = XFixedToDouble(a->dx);
double a_dy = XFixedToDouble(a->dy);
double b_dx = XFixedToDouble(b->dx);
double b_dy = XFixedToDouble(b->dy);
return b_dy * a_dx > a_dy * b_dx;
}
static int
_SlopeCounterClockwise(XrSlopeFixed *a, XrSlopeFixed *b)
{
return ! _SlopeClockwise(a, b);
}
/* Find active pen vertex for clockwise edge of stroke at the given slope.
*
* NOTE: The behavior of this function is sensitive to the sense of
* the inequality within _SlopeClockwise/_SlopeCounterClockwise.
*
* The issue is that the slope_ccw member of one pen vertex will be
* equivalent to the slope_cw member of the next pen vertex in a
* counterclockwise order. However, for this function, we care
* strongly about which vertex is returned.
*/
XrStatus
_XrPenFindActiveCWVertexIndex(XrPen *pen, XrSlopeFixed *slope, int *active)
{
int i;
for (i=0; i < pen->num_vertices; i++) {
if (_SlopeClockwise(slope, &pen->vertex[i].slope_ccw)
&& _SlopeCounterClockwise(slope, &pen->vertex[i].slope_cw))
break;
}
*active = i;
return XrStatusSuccess;
}
/* Find active pen vertex for counterclockwise edge of stroke at the given slope.
*
* NOTE: The behavior of this function is sensitive to the sense of
* the inequality within _SlopeClockwise/_SlopeCounterClockwise.
*/
XrStatus
_XrPenFindActiveCCWVertexIndex(XrPen *pen, XrSlopeFixed *slope, int *active)
{
int i;
XrSlopeFixed slope_reverse;
slope_reverse = *slope;
slope_reverse.dx = -slope_reverse.dx;
slope_reverse.dy = -slope_reverse.dy;
for (i=pen->num_vertices-1; i >= 0; i--) {
if (_SlopeCounterClockwise(&pen->vertex[i].slope_ccw, &slope_reverse)
&& _SlopeClockwise(&pen->vertex[i].slope_cw, &slope_reverse))
break;
}
*active = i;
return XrStatusSuccess;
}
static XrStatus
_XrPenStrokeSplineHalf(XrPen *pen, XrSpline *spline, XrPenStrokeDirection dir, XrPolygon *polygon)
{
int i;
XrStatus status;
int start, stop, step;
int active = 0;
XPointFixed hull_pt;
XrSlopeFixed slope, initial_slope, final_slope;
XPointFixed *pt = spline->pts;
int num_pts = spline->num_pts;
if (dir == XrPenStrokeDirectionForward) {
start = 0;
stop = num_pts;
step = 1;
initial_slope = spline->initial_slope;
final_slope = spline->final_slope;
} else {
start = num_pts - 1;
stop = -1;
step = -1;
initial_slope = spline->final_slope;
initial_slope.dx = -initial_slope.dx;
initial_slope.dy = -initial_slope.dy;
final_slope = spline->initial_slope;
final_slope.dx = -final_slope.dx;
final_slope.dy = -final_slope.dy;
}
_XrPenFindActiveCWVertexIndex(pen, &initial_slope, &active);
i = start;
while (i != stop) {
hull_pt.x = pt[i].x + pen->vertex[active].pt.x;
hull_pt.y = pt[i].y + pen->vertex[active].pt.y;
status = _XrPolygonAddPoint(polygon, &hull_pt);
if (status)
return status;
if (i + step == stop)
slope = final_slope;
else
_ComputeSlope(&pt[i], &pt[i+step], &slope);
if (_SlopeCounterClockwise(&slope, &pen->vertex[active].slope_ccw)) {
if (++active == pen->num_vertices)
active = 0;
} else if (_SlopeClockwise(&slope, &pen->vertex[active].slope_cw)) {
if (--active == -1)
active = pen->num_vertices - 1;
} else {
i += step;
}
}
return XrStatusSuccess;
}
/* Compute outline of a given spline using the pen.
The trapezoids needed to fill that outline will be added to traps
*/
XrStatus
_XrPenStrokeSpline(XrPen *pen,
XrSpline *spline,
double tolerance,
XrTraps *traps)
{
XrStatus status;
XrPolygon polygon;
_XrPolygonInit(&polygon);
status = _XrSplineDecompose(spline, tolerance);
if (status)
return status;
status = _XrPenStrokeSplineHalf(pen, spline, XrPenStrokeDirectionForward, &polygon);
if (status)
return status;
status = _XrPenStrokeSplineHalf(pen, spline, XrPenStrokeDirectionReverse, &polygon);
if (status)
return status;
_XrPolygonClose(&polygon);
_XrTrapsTessellatePolygon(traps, &polygon, XrFillRuleWinding);
_XrPolygonDeinit(&polygon);
return XrStatusSuccess;
}