| //======================================================================== |
| // |
| // 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, 2011, 2018, 2019, 2021 Albert Astals Cid <aacid@kde.org> |
| // Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de> |
| // Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com> |
| // |
| // 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> |
| |
| #include <cstdlib> |
| #include <cstring> |
| #include <algorithm> |
| #include "goo/gmem.h" |
| #include "goo/GooLikely.h" |
| #include "SplashMath.h" |
| #include "SplashPath.h" |
| #include "SplashXPath.h" |
| |
| //------------------------------------------------------------------------ |
| |
| struct SplashXPathPoint |
| { |
| SplashCoord x, y; |
| }; |
| |
| struct SplashXPathAdjust |
| { |
| int firstPt, lastPt; // range of points |
| bool 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(SplashPath *path, SplashCoord *matrix, SplashCoord flatness, bool closeSubpaths, bool adjustLines, int linePosI) |
| { |
| SplashPathHint *hint; |
| SplashXPathPoint *pts; |
| SplashXPathAdjust *adjusts, *adjust; |
| SplashCoord x0, y0, x1, y1, x2, y2, x3, y3, xsp, ysp; |
| SplashCoord adj0, adj1; |
| int curSubpath, 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_checkoverflow(path->hintsLength, sizeof(SplashXPathAdjust)); |
| if (adjusts) { |
| 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 = nullptr; |
| 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 = true; |
| adj0 = x0; |
| adj1 = x2; |
| } else if (y0 == y1 && y2 == y3) { |
| adjusts[i].vert = false; |
| adj0 = y0; |
| adj1 = y2; |
| } else { |
| gfree(adjusts); |
| adjusts = nullptr; |
| break; |
| } |
| if (adj0 > adj1) { |
| x0 = adj0; |
| adj0 = adj1; |
| adj1 = x0; |
| } |
| 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; |
| // rounding both edge coordinates can result in lines of |
| // different widths (e.g., adj=10.1, adj1=11.3 --> x0=10, x1=11; |
| // adj0=10.4, adj1=11.6 --> x0=10, x1=12), but it has the |
| // benefit of making adjacent strokes/fills line up without any |
| // gaps between them |
| x0 = splashRound(adj0); |
| x1 = splashRound(adj1); |
| if (x1 == x0) { |
| if (adjustLines) { |
| // the adjustment moves thin lines (clip rectangle with |
| // empty width or height) out of clip area, here we need |
| // a special adjustment: |
| x0 = linePosI; |
| x1 = x0 + 1; |
| } else { |
| x1 = x1 + 1; |
| } |
| } |
| adjusts[i].x0 = (SplashCoord)x0; |
| adjusts[i].x1 = (SplashCoord)x1 - 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 = nullptr; |
| } |
| |
| // 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 = nullptr; |
| length = size = 0; |
| |
| x0 = y0 = xsp = ysp = 0; // make gcc happy |
| adj0 = adj1 = 0; // make gcc happy |
| curSubpath = 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; |
| ++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); |
| 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); |
| } |
| } |
| } |
| |
| 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() |
| { |
| 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_checkoverflow(segs, size, sizeof(SplashXPathSeg)); |
| if (unlikely(!segs)) { |
| length = 0; |
| size = 0; |
| } |
| } |
| } |
| |
| void SplashXPath::addCurve(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1, SplashCoord x2, SplashCoord y2, SplashCoord x3, SplashCoord y3, SplashCoord flatness, bool first, bool last, bool end0, bool end1) |
| { |
| SplashCoord *cx = new SplashCoord[(splashMaxCurveSplits + 1) * 3]; |
| SplashCoord *cy = new SplashCoord[(splashMaxCurveSplits + 1) * 3]; |
| int *cNext = new int[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 * 3 + 0) = x0; |
| *(cx + p1 * 3 + 1) = x1; |
| *(cx + p1 * 3 + 2) = x2; |
| *(cx + p2 * 3 + 0) = x3; |
| |
| *(cy + p1 * 3 + 0) = y0; |
| *(cy + p1 * 3 + 1) = y1; |
| *(cy + p1 * 3 + 2) = y2; |
| *(cy + p2 * 3 + 0) = y3; |
| |
| *(cNext + p1) = p2; |
| |
| while (p1 < splashMaxCurveSplits) { |
| |
| // get the next segment |
| xl0 = *(cx + p1 * 3 + 0); |
| xx1 = *(cx + p1 * 3 + 1); |
| xx2 = *(cx + p1 * 3 + 2); |
| |
| yl0 = *(cy + p1 * 3 + 0); |
| yy1 = *(cy + p1 * 3 + 1); |
| yy2 = *(cy + p1 * 3 + 2); |
| |
| p2 = *(cNext + p1); |
| |
| xr3 = *(cx + p2 * 3 + 0); |
| yr3 = *(cy + p2 * 3 + 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 = 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 * 3 + 1) = xl1; |
| *(cx + p1 * 3 + 2) = xl2; |
| |
| *(cy + p1 * 3 + 1) = yl1; |
| *(cy + p1 * 3 + 2) = yl2; |
| |
| *(cNext + p1) = p3; |
| |
| *(cx + p3 * 3 + 0) = xr0; |
| *(cx + p3 * 3 + 1) = xr1; |
| *(cx + p3 * 3 + 2) = xr2; |
| |
| *(cy + p3 * 3 + 0) = yr0; |
| *(cy + p3 * 3 + 1) = yr1; |
| *(cy + p3 * 3 + 2) = yr2; |
| |
| *(cNext + p3) = p2; |
| } |
| } |
| |
| delete[] cx; |
| delete[] cy; |
| delete[] cNext; |
| } |
| |
| void SplashXPath::addSegment(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1) |
| { |
| grow(1); |
| if (unlikely(!segs)) { |
| return; |
| } |
| segs[length].x0 = x0; |
| segs[length].y0 = y0; |
| segs[length].x1 = x1; |
| segs[length].y1 = y1; |
| segs[length].flags = 0; |
| 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; |
| } |
| |
| struct cmpXPathSegsFunctor |
| { |
| bool operator()(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; |
| } |
| return (y0 != y1) ? (y0 < y1) : (x0 < x1); |
| } |
| }; |
| |
| 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, cmpXPathSegsFunctor()); |
| } |