blob: d5589354587bf43e2e683d1b1301d63987d85a86 [file] [log] [blame]
//========================================================================
//
// SplashXPath.cc
//
//========================================================================
//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
// Copyright (C) 2010 Albert Astals Cid <aacid@kde.org>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================
#include <config.h>
#ifdef USE_GCC_PRAGMAS
#pragma implementation
#endif
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include "goo/gmem.h"
#include "SplashMath.h"
#include "SplashPath.h"
#include "SplashXPath.h"
//------------------------------------------------------------------------
struct SplashXPathPoint {
SplashCoord x, y;
};
struct SplashXPathAdjust {
int firstPt, lastPt; // range of points
GBool vert; // vertical or horizontal hint
SplashCoord x0a, x0b, // hint boundaries
xma, xmb,
x1a, x1b;
SplashCoord x0, x1, xm; // adjusted coordinates
};
//------------------------------------------------------------------------
// Transform a point from user space to device space.
inline void SplashXPath::transform(SplashCoord *matrix,
SplashCoord xi, SplashCoord yi,
SplashCoord *xo, SplashCoord *yo) {
// [ m[0] m[1] 0 ]
// [xo yo 1] = [xi yi 1] * [ m[2] m[3] 0 ]
// [ m[4] m[5] 1 ]
*xo = xi * matrix[0] + yi * matrix[2] + matrix[4];
*yo = xi * matrix[1] + yi * matrix[3] + matrix[5];
}
//------------------------------------------------------------------------
// SplashXPath
//------------------------------------------------------------------------
SplashXPath::SplashXPath() {
segs = NULL;
length = size = 0;
}
SplashXPath::SplashXPath(SplashPath *path, SplashCoord *matrix,
SplashCoord flatness, GBool closeSubpaths) {
SplashPathHint *hint;
SplashXPathPoint *pts;
SplashXPathAdjust *adjusts, *adjust;
SplashCoord x0, y0, x1, y1, x2, y2, x3, y3, xsp, ysp;
SplashCoord adj0, adj1, w;
int ww;
int curSubpath, curSubpathX, i, j;
// transform the points
pts = (SplashXPathPoint *)gmallocn(path->length, sizeof(SplashXPathPoint));
for (i = 0; i < path->length; ++i) {
transform(matrix, path->pts[i].x, path->pts[i].y, &pts[i].x, &pts[i].y);
}
// set up the stroke adjustment hints
if (path->hints) {
adjusts = (SplashXPathAdjust *)gmallocn(path->hintsLength,
sizeof(SplashXPathAdjust));
for (i = 0; i < path->hintsLength; ++i) {
hint = &path->hints[i];
if (hint->ctrl0 + 1 >= path->length || hint->ctrl1 + 1 >= path->length) {
gfree(adjusts);
adjusts = NULL;
break;
}
x0 = pts[hint->ctrl0 ].x; y0 = pts[hint->ctrl0 ].y;
x1 = pts[hint->ctrl0 + 1].x; y1 = pts[hint->ctrl0 + 1].y;
x2 = pts[hint->ctrl1 ].x; y2 = pts[hint->ctrl1 ].y;
x3 = pts[hint->ctrl1 + 1].x; y3 = pts[hint->ctrl1 + 1].y;
if (x0 == x1 && x2 == x3) {
adjusts[i].vert = gTrue;
adj0 = x0;
adj1 = x2;
} else if (y0 == y1 && y2 == y3) {
adjusts[i].vert = gFalse;
adj0 = y0;
adj1 = y2;
} else {
gfree(adjusts);
adjusts = NULL;
break;
}
if (adj0 > adj1) {
x0 = adj0;
adj0 = adj1;
adj1 = x0;
}
w = adj1 - adj0;
ww = splashRound(w);
if (ww == 0) {
ww = 1;
}
adjusts[i].x0a = adj0 - 0.01;
adjusts[i].x0b = adj0 + 0.01;
adjusts[i].xma = (SplashCoord)0.5 * (adj0 + adj1) - 0.01;
adjusts[i].xmb = (SplashCoord)0.5 * (adj0 + adj1) + 0.01;
adjusts[i].x1a = adj1 - 0.01;
adjusts[i].x1b = adj1 + 0.01;
adjusts[i].x0 = (SplashCoord)splashRound(adj0);
adjusts[i].x1 = adjusts[i].x0 + ww - 0.01;
adjusts[i].xm = (SplashCoord)0.5 * (adjusts[i].x0 + adjusts[i].x1);
adjusts[i].firstPt = hint->firstPt;
adjusts[i].lastPt = hint->lastPt;
}
} else {
adjusts = NULL;
}
// perform stroke adjustment
if (adjusts) {
for (i = 0, adjust = adjusts; i < path->hintsLength; ++i, ++adjust) {
for (j = adjust->firstPt; j <= adjust->lastPt; ++j) {
strokeAdjust(adjust, &pts[j].x, &pts[j].y);
}
}
gfree(adjusts);
}
segs = NULL;
length = size = 0;
x0 = y0 = xsp = ysp = 0; // make gcc happy
adj0 = adj1 = 0; // make gcc happy
curSubpath = 0;
curSubpathX = 0;
i = 0;
while (i < path->length) {
// first point in subpath - skip it
if (path->flags[i] & splashPathFirst) {
x0 = pts[i].x;
y0 = pts[i].y;
xsp = x0;
ysp = y0;
curSubpath = i;
curSubpathX = length;
++i;
} else {
// curve segment
if (path->flags[i] & splashPathCurve) {
x1 = pts[i].x;
y1 = pts[i].y;
x2 = pts[i+1].x;
y2 = pts[i+1].y;
x3 = pts[i+2].x;
y3 = pts[i+2].y;
addCurve(x0, y0, x1, y1, x2, y2, x3, y3,
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));
x0 = x3;
y0 = y3;
i += 3;
// line segment
} else {
x1 = pts[i].x;
y1 = pts[i].y;
addSegment(x0, y0, x1, y1,
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));
x0 = x1;
y0 = y1;
++i;
}
// close a subpath
if (closeSubpaths &&
(path->flags[i-1] & splashPathLast) &&
(pts[i-1].x != pts[curSubpath].x ||
pts[i-1].y != pts[curSubpath].y)) {
addSegment(x0, y0, xsp, ysp,
gFalse, gTrue, gFalse, gFalse);
}
}
}
gfree(pts);
}
// Apply the stroke adjust hints to point <pt>: (*<xp>, *<yp>).
void SplashXPath::strokeAdjust(SplashXPathAdjust *adjust,
SplashCoord *xp, SplashCoord *yp) {
SplashCoord x, y;
if (adjust->vert) {
x = *xp;
if (x > adjust->x0a && x < adjust->x0b) {
*xp = adjust->x0;
} else if (x > adjust->xma && x < adjust->xmb) {
*xp = adjust->xm;
} else if (x > adjust->x1a && x < adjust->x1b) {
*xp = adjust->x1;
}
} else {
y = *yp;
if (y > adjust->x0a && y < adjust->x0b) {
*yp = adjust->x0;
} else if (y > adjust->xma && y < adjust->xmb) {
*yp = adjust->xm;
} else if (y > adjust->x1a && y < adjust->x1b) {
*yp = adjust->x1;
}
}
}
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[splashMaxCurveSplits + 1][3];
SplashCoord cy[splashMaxCurveSplits + 1][3];
int cNext[splashMaxCurveSplits + 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 = splashMaxCurveSplits;
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 < splashMaxCurveSplits) {
// 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 == splashMaxCurveSplits && last,
p1 == 0 && end0,
p2 == splashMaxCurveSplits && 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::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 {
#if USE_FIXEDPOINT
if (FixedPoint::divCheck(x1 - x0, y1 - y0, &segs[length].dxdy)) {
segs[length].dydx = (SplashCoord)1 / segs[length].dxdy;
} else {
segs[length].dxdy = segs[length].dydx = 0;
if (splashAbs(x1 - x0) > splashAbs(y1 - y0)) {
segs[length].flags |= splashXPathHoriz;
} else {
segs[length].flags |= splashXPathVert;
}
}
#else
segs[length].dxdy = (x1 - x0) / (y1 - y0);
segs[length].dydx = (SplashCoord)1 / segs[length].dxdy;
#endif
}
if (y0 > y1) {
segs[length].flags |= splashXPathFlip;
}
++length;
}
static bool cmpXPathSegs(const SplashXPathSeg &seg0, const SplashXPathSeg &seg1) {
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) ? true : false;
}
if (x0 != x1) {
return (x0 < x1) ? true : false;
}
return false;
}
void SplashXPath::aaScale() {
SplashXPathSeg *seg;
int i;
for (i = 0, seg = segs; i < length; ++i, ++seg) {
seg->x0 *= splashAASize;
seg->y0 *= splashAASize;
seg->x1 *= splashAASize;
seg->y1 *= splashAASize;
}
}
void SplashXPath::sort() {
std::sort(segs, segs + length, &cmpXPathSegs);
}