blob: 7fa0865abe76aaacdb093357ec6dac31efba7f46 [file] [log] [blame]
//========================================================================
//
// SplashXPath.cc
//
//========================================================================
#include <config.h>
#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif
#include <stdlib.h>
#include <string.h>
#include "goo/gmem.h"
#include "SplashMath.h"
#include "SplashPath.h"
#include "SplashXPath.h"
//------------------------------------------------------------------------
#define maxCurveSplits (1 << 10)
//------------------------------------------------------------------------
// SplashXPath
//------------------------------------------------------------------------
SplashXPath::SplashXPath() {
segs = NULL;
length = size = 0;
}
SplashXPath::SplashXPath(SplashPath *path, SplashCoord flatness,
GBool closeSubpaths) {
SplashCoord xc, yc, dx, dy, r, x0, y0, x1, y1;
int quad0, quad1, quad;
int curSubpath, n, i, j;
segs = NULL;
length = size = 0;
i = 0;
curSubpath = 0;
while (i < path->length) {
// first point in subpath - skip it
if (path->flags[i] & splashPathFirst) {
curSubpath = i;
++i;
} else {
// curve segment
if (path->flags[i] & splashPathCurve) {
addCurve(path->pts[i-1].x, path->pts[i-1].y,
path->pts[i ].x, path->pts[i ].y,
path->pts[i+1].x, path->pts[i+1].y,
path->pts[i+2].x, path->pts[i+2].y,
flatness,
(path->flags[i-1] & splashPathFirst),
(path->flags[i+2] & splashPathLast),
!closeSubpaths &&
(path->flags[i-1] & splashPathFirst) &&
!(path->flags[i-1] & splashPathClosed),
!closeSubpaths &&
(path->flags[i+2] & splashPathLast) &&
!(path->flags[i+2] & splashPathClosed));
i += 3;
// clockwise circular arc
} else if (path->flags[i] & splashPathArcCW) {
xc = path->pts[i].x;
yc = path->pts[i].y;
dx = path->pts[i+1].x - xc;
dy = path->pts[i+1].y - yc;
r = splashSqrt(dx * dx + dy * dy);
if (path->pts[i-1].x < xc && path->pts[i-1].y <= yc) {
quad0 = 0;
} else if (path->pts[i-1].x >= xc && path->pts[i-1].y < yc) {
quad0 = 1;
} else if (path->pts[i-1].x > xc && path->pts[i-1].y >= yc) {
quad0 = 2;
} else {
quad0 = 3;
}
if (path->pts[i+1].x <= xc && path->pts[i+1].y < yc) {
quad1 = 0;
} else if (path->pts[i+1].x > xc && path->pts[i+1].y <= yc) {
quad1 = 1;
} else if (path->pts[i+1].x >= xc && path->pts[i+1].y > yc) {
quad1 = 2;
} else {
quad1 = 3;
}
n = 0; // make gcc happy
if (quad0 == quad1) {
switch (quad0) {
case 0:
case 1: n = path->pts[i-1].x < path->pts[i+1].x ? 0 : 4; break;
case 2:
case 3: n = path->pts[i-1].x > path->pts[i+1].x ? 0 : 4; break;
}
} else {
n = (quad1 - quad0) & 3;
}
x0 = path->pts[i-1].x;
y0 = path->pts[i-1].y;
x1 = y1 = 0; // make gcc happy
quad = quad0;
for (j = 0; j < n; ++j) {
switch (quad) {
case 0: x1 = xc; y1 = yc - r; break;
case 1: x1 = xc + r; y1 = yc; break;
case 2: x1 = xc; y1 = yc + r; break;
case 3: x1 = xc - r; y1 = yc; break;
}
addArc(x0, y0, x1, y1,
xc, yc, r, quad, flatness,
quad == quad0 && (path->flags[i-1] & splashPathFirst),
gFalse,
quad == quad0 && !closeSubpaths &&
(path->flags[i-1] & splashPathFirst) &&
!(path->flags[i-1] & splashPathClosed),
gFalse);
x0 = x1;
y0 = y1;
quad = (quad + 1) & 3;
}
addArc(x0, y0, path->pts[i+1].x, path->pts[i+1].y,
xc, yc, r, quad, flatness,
quad == quad0 && (path->flags[i-1] & splashPathFirst),
(path->flags[i+1] & splashPathLast),
quad == quad0 && !closeSubpaths &&
(path->flags[i-1] & splashPathFirst) &&
!(path->flags[i-1] & splashPathClosed),
!closeSubpaths &&
(path->flags[i+1] & splashPathLast) &&
!(path->flags[i+1] & splashPathClosed));
i += 2;
// line segment
} else {
addSegment(path->pts[i-1].x, path->pts[i-1].y,
path->pts[i].x, path->pts[i].y,
path->flags[i-1] & splashPathFirst,
path->flags[i] & splashPathLast,
!closeSubpaths &&
(path->flags[i-1] & splashPathFirst) &&
!(path->flags[i-1] & splashPathClosed),
!closeSubpaths &&
(path->flags[i] & splashPathLast) &&
!(path->flags[i] & splashPathClosed));
++i;
}
// close a subpath
if (closeSubpaths &&
(path->flags[i-1] & splashPathLast) &&
(path->pts[i-1].x != path->pts[curSubpath].x ||
path->pts[i-1].y != path->pts[curSubpath]. y)) {
addSegment(path->pts[i-1].x, path->pts[i-1].y,
path->pts[curSubpath].x, path->pts[curSubpath].y,
gFalse, gTrue, gFalse, gFalse);
}
}
}
}
SplashXPath::SplashXPath(SplashXPath *xPath) {
length = xPath->length;
size = xPath->size;
segs = (SplashXPathSeg *)gmallocn(size, sizeof(SplashXPathSeg));
memcpy(segs, xPath->segs, length * sizeof(SplashXPathSeg));
}
SplashXPath::~SplashXPath() {
gfree(segs);
}
// Add space for <nSegs> more segments
void SplashXPath::grow(int nSegs) {
if (length + nSegs > size) {
if (size == 0) {
size = 32;
}
while (size < length + nSegs) {
size *= 2;
}
segs = (SplashXPathSeg *)greallocn(segs, size, sizeof(SplashXPathSeg));
}
}
void SplashXPath::addCurve(SplashCoord x0, SplashCoord y0,
SplashCoord x1, SplashCoord y1,
SplashCoord x2, SplashCoord y2,
SplashCoord x3, SplashCoord y3,
SplashCoord flatness,
GBool first, GBool last, GBool end0, GBool end1) {
SplashCoord cx[maxCurveSplits + 1][3];
SplashCoord cy[maxCurveSplits + 1][3];
int cNext[maxCurveSplits + 1];
SplashCoord xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh;
SplashCoord yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh;
SplashCoord dx, dy, mx, my, d1, d2, flatness2;
int p1, p2, p3;
flatness2 = flatness * flatness;
// initial segment
p1 = 0;
p2 = maxCurveSplits;
cx[p1][0] = x0; cy[p1][0] = y0;
cx[p1][1] = x1; cy[p1][1] = y1;
cx[p1][2] = x2; cy[p1][2] = y2;
cx[p2][0] = x3; cy[p2][0] = y3;
cNext[p1] = p2;
while (p1 < maxCurveSplits) {
// get the next segment
xl0 = cx[p1][0]; yl0 = cy[p1][0];
xx1 = cx[p1][1]; yy1 = cy[p1][1];
xx2 = cx[p1][2]; yy2 = cy[p1][2];
p2 = cNext[p1];
xr3 = cx[p2][0]; yr3 = cy[p2][0];
// compute the distances from the control points to the
// midpoint of the straight line (this is a bit of a hack, but
// it's much faster than computing the actual distances to the
// line)
mx = (xl0 + xr3) * 0.5;
my = (yl0 + yr3) * 0.5;
dx = xx1 - mx;
dy = yy1 - my;
d1 = dx*dx + dy*dy;
dx = xx2 - mx;
dy = yy2 - my;
d2 = dx*dx + dy*dy;
// if the curve is flat enough, or no more subdivisions are
// allowed, add the straight line segment
if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) {
addSegment(xl0, yl0, xr3, yr3,
p1 == 0 && first,
p2 == maxCurveSplits && last,
p1 == 0 && end0,
p2 == maxCurveSplits && end1);
p1 = p2;
// otherwise, subdivide the curve
} else {
xl1 = (xl0 + xx1) * 0.5;
yl1 = (yl0 + yy1) * 0.5;
xh = (xx1 + xx2) * 0.5;
yh = (yy1 + yy2) * 0.5;
xl2 = (xl1 + xh) * 0.5;
yl2 = (yl1 + yh) * 0.5;
xr2 = (xx2 + xr3) * 0.5;
yr2 = (yy2 + yr3) * 0.5;
xr1 = (xh + xr2) * 0.5;
yr1 = (yh + yr2) * 0.5;
xr0 = (xl2 + xr1) * 0.5;
yr0 = (yl2 + yr1) * 0.5;
// add the new subdivision points
p3 = (p1 + p2) / 2;
cx[p1][1] = xl1; cy[p1][1] = yl1;
cx[p1][2] = xl2; cy[p1][2] = yl2;
cNext[p1] = p3;
cx[p3][0] = xr0; cy[p3][0] = yr0;
cx[p3][1] = xr1; cy[p3][1] = yr1;
cx[p3][2] = xr2; cy[p3][2] = yr2;
cNext[p3] = p2;
}
}
}
void SplashXPath::addArc(SplashCoord x0, SplashCoord y0,
SplashCoord x1, SplashCoord y1,
SplashCoord xc, SplashCoord yc,
SplashCoord r, int quad,
SplashCoord flatness,
GBool first, GBool last, GBool end0, GBool end1) {
SplashCoord px[maxCurveSplits + 1];
SplashCoord py[maxCurveSplits + 1];
int pNext[maxCurveSplits + 1];
SplashCoord r2, flatness2;
SplashCoord xx0, yy0, xx1, yy1, xm, ym, t, dx, dy;
int p1, p2, p3;
r2 = r * r;
flatness2 = flatness * flatness;
// initial segment
p1 = 0;
p2 = maxCurveSplits;
px[p1] = x0; py[p1] = y0;
px[p2] = x1; py[p2] = y1;
pNext[p1] = p2;
while (p1 < maxCurveSplits) {
// get the next segment
xx0 = px[p1]; yy0 = py[p1];
p2 = pNext[p1];
xx1 = px[p2]; yy1 = py[p2];
// compute the arc midpoint
t = (xx0 - xc) * (xx1 - xc) - (yy0 - yc) * (yy1 - yc);
xm = splashSqrt((SplashCoord)0.5 * (r2 + t));
ym = splashSqrt((SplashCoord)0.5 * (r2 - t));
switch (quad) {
case 0: xm = xc - xm; ym = yc - ym; break;
case 1: xm = xc + xm; ym = yc - ym; break;
case 2: xm = xc + xm; ym = yc + ym; break;
case 3: xm = xc - xm; ym = yc + ym; break;
}
// compute distance from midpoint of straight segment to midpoint
// of arc
dx = (SplashCoord)0.5 * (xx0 + xx1) - xm;
dy = (SplashCoord)0.5 * (yy0 + yy1) - ym;
// if the arc is flat enough, or no more subdivisions are allowed,
// add the straight line segment
if (p2 - p1 == 1 || dx * dx + dy * dy <= flatness2) {
addSegment(xx0, yy0, xx1, yy1,
p1 == 0 && first,
p2 == maxCurveSplits && last,
p1 == 0 && end0,
p2 == maxCurveSplits && end1);
p1 = p2;
// otherwise, subdivide the arc
} else {
p3 = (p1 + p2) / 2;
px[p3] = xm;
py[p3] = ym;
pNext[p1] = p3;
pNext[p3] = p2;
}
}
}
void SplashXPath::addSegment(SplashCoord x0, SplashCoord y0,
SplashCoord x1, SplashCoord y1,
GBool first, GBool last, GBool end0, GBool end1) {
grow(1);
segs[length].x0 = x0;
segs[length].y0 = y0;
segs[length].x1 = x1;
segs[length].y1 = y1;
segs[length].flags = 0;
if (first) {
segs[length].flags |= splashXPathFirst;
}
if (last) {
segs[length].flags |= splashXPathLast;
}
if (end0) {
segs[length].flags |= splashXPathEnd0;
}
if (end1) {
segs[length].flags |= splashXPathEnd1;
}
if (y1 == y0) {
segs[length].dxdy = segs[length].dydx = 0;
segs[length].flags |= splashXPathHoriz;
if (x1 == x0) {
segs[length].flags |= splashXPathVert;
}
} else if (x1 == x0) {
segs[length].dxdy = segs[length].dydx = 0;
segs[length].flags |= splashXPathVert;
} else {
segs[length].dxdy = (x1 - x0) / (y1 - y0);
segs[length].dydx = (SplashCoord)1 / segs[length].dxdy;
}
if (y0 > y1) {
segs[length].flags |= splashXPathFlip;
}
++length;
}
static int cmpXPathSegs(const void *arg0, const void *arg1) {
SplashXPathSeg *seg0 = (SplashXPathSeg *)arg0;
SplashXPathSeg *seg1 = (SplashXPathSeg *)arg1;
SplashCoord x0, y0, x1, y1;
if (seg0->flags & splashXPathFlip) {
x0 = seg0->x1;
y0 = seg0->y1;
} else {
x0 = seg0->x0;
y0 = seg0->y0;
}
if (seg1->flags & splashXPathFlip) {
x1 = seg1->x1;
y1 = seg1->y1;
} else {
x1 = seg1->x0;
y1 = seg1->y0;
}
if (y0 != y1) {
return (y0 > y1) ? 1 : -1;
}
if (x0 != x1) {
return (x0 > x1) ? 1 : -1;
}
return 0;
}
void SplashXPath::sort() {
qsort(segs, length, sizeof(SplashXPathSeg), &cmpXPathSegs);
}