blob: 78970f4eeff46da9b802bb3c8e6f213f8c88aeac [file] [log] [blame] [edit]
//========================================================================
//
// SplashXPathScanner.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) 2008, 2010, 2014, 2018, 2019, 2021, 2022, 2024 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
// Copyright (C) 2013, 2014, 2021 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2018 Stefan Brüns <stefan.bruens@rwth-aachen.de>
//
// 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 "SplashXPath.h"
#include "SplashBitmap.h"
#include "SplashXPathScanner.h"
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// SplashXPathScanner
//------------------------------------------------------------------------
SplashXPathScanner::SplashXPathScanner(const SplashXPath &xPath, bool eoA, int clipYMin, int clipYMax)
{
const SplashXPathSeg *seg;
SplashCoord xMinFP, yMinFP, xMaxFP, yMaxFP;
int i;
eo = eoA;
partialClip = false;
// compute the bbox
xMin = yMin = 1;
xMax = yMax = 0;
if (xPath.length > 0) {
seg = &xPath.segs[0];
if (unlikely(std::isnan(seg->x0) || std::isnan(seg->x1) || std::isnan(seg->y0) || std::isnan(seg->y1))) {
return;
}
if (seg->x0 <= seg->x1) {
xMinFP = seg->x0;
xMaxFP = seg->x1;
} else {
xMinFP = seg->x1;
xMaxFP = seg->x0;
}
if (seg->flags & splashXPathFlip) {
yMinFP = seg->y1;
yMaxFP = seg->y0;
} else {
yMinFP = seg->y0;
yMaxFP = seg->y1;
}
for (i = 1; i < xPath.length; ++i) {
seg = &xPath.segs[i];
if (unlikely(std::isnan(seg->x0) || std::isnan(seg->x1) || std::isnan(seg->y0) || std::isnan(seg->y1))) {
return;
}
if (seg->x0 < xMinFP) {
xMinFP = seg->x0;
} else if (seg->x0 > xMaxFP) {
xMaxFP = seg->x0;
}
if (seg->x1 < xMinFP) {
xMinFP = seg->x1;
} else if (seg->x1 > xMaxFP) {
xMaxFP = seg->x1;
}
if (seg->flags & splashXPathFlip) {
if (seg->y0 > yMaxFP) {
yMaxFP = seg->y0;
}
} else {
if (seg->y1 > yMaxFP) {
yMaxFP = seg->y1;
}
}
}
xMin = splashFloor(xMinFP);
xMax = splashFloor(xMaxFP);
yMin = splashFloor(yMinFP);
yMax = splashFloor(yMaxFP);
if (clipYMin > yMin) {
yMin = clipYMin;
partialClip = true;
}
if (clipYMax < yMax) {
yMax = clipYMax;
partialClip = true;
}
}
computeIntersections(xPath);
}
SplashXPathScanner::~SplashXPathScanner() { }
void SplashXPathScanner::getBBoxAA(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA) const
{
*xMinA = xMin / splashAASize;
*yMinA = yMin / splashAASize;
*xMaxA = xMax / splashAASize;
*yMaxA = yMax / splashAASize;
}
void SplashXPathScanner::getSpanBounds(int y, int *spanXMin, int *spanXMax) const
{
if (y < yMin || y > yMax) {
*spanXMin = xMax + 1;
*spanXMax = xMax;
return;
}
const auto &line = allIntersections[y - yMin];
if (!line.empty()) {
*spanXMin = line[0].x0;
int xx = line[0].x1;
for (const SplashIntersect &intersect : line) {
if (intersect.x1 > xx) {
xx = intersect.x1;
}
}
*spanXMax = xx;
} else {
*spanXMin = xMax + 1;
*spanXMax = xMax;
}
}
bool SplashXPathScanner::test(int x, int y) const
{
if (y < yMin || y > yMax) {
return false;
}
const auto &line = allIntersections[y - yMin];
int count = 0;
for (unsigned int i = 0; i < line.size() && line[i].x0 <= x; ++i) {
if (x <= line[i].x1) {
return true;
}
count += line[i].count;
}
return eo ? (count & 1) : (count != 0);
}
bool SplashXPathScanner::testSpan(int x0, int x1, int y) const
{
unsigned int i;
if (y < yMin || y > yMax) {
return false;
}
const auto &line = allIntersections[y - yMin];
int count = 0;
for (i = 0; i < line.size() && line[i].x1 < x0; ++i) {
count += line[i].count;
}
// invariant: the subspan [x0,xx1] is inside the path
int xx1 = x0 - 1;
while (xx1 < x1) {
if (i >= line.size()) {
return false;
}
if (line[i].x0 > xx1 + 1 && !(eo ? (count & 1) : (count != 0))) {
return false;
}
if (line[i].x1 > xx1) {
xx1 = line[i].x1;
}
count += line[i].count;
++i;
}
return true;
}
bool SplashXPathScanIterator::getNextSpan(int *x0, int *x1)
{
int xx0, xx1;
if (interIdx >= line.size()) {
return false;
}
xx0 = line[interIdx].x0;
xx1 = line[interIdx].x1;
interCount += line[interIdx].count;
++interIdx;
while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (eo ? (interCount & 1) : (interCount != 0)))) {
if (line[interIdx].x1 > xx1) {
xx1 = line[interIdx].x1;
}
interCount += line[interIdx].count;
++interIdx;
}
*x0 = xx0;
*x1 = xx1;
return true;
}
SplashXPathScanIterator::SplashXPathScanIterator(const SplashXPathScanner &scanner, int y)
: line((y < scanner.yMin || y > scanner.yMax) ? scanner.allIntersections[0] : scanner.allIntersections[y - scanner.yMin]), interIdx(0), interCount(0), eo(scanner.eo)
{
if (y < scanner.yMin || y > scanner.yMax) {
// set index to line end
interIdx = line.size();
}
}
void SplashXPathScanner::computeIntersections(const SplashXPath &xPath)
{
const SplashXPathSeg *seg;
SplashCoord segXMin, segXMax, segYMin, segYMax, xx0, xx1;
int x, y, y0, y1, i;
if (yMin > yMax) {
return;
}
// build the list of all intersections
allIntersections.resize(yMax - yMin + 1);
for (i = 0; i < xPath.length; ++i) {
seg = &xPath.segs[i];
if (seg->flags & splashXPathFlip) {
segYMin = seg->y1;
segYMax = seg->y0;
} else {
segYMin = seg->y0;
segYMax = seg->y1;
}
if (seg->flags & splashXPathHoriz) {
y = splashFloor(seg->y0);
if (y >= yMin && y <= yMax) {
if (!addIntersection(segYMin, segYMax, y, splashFloor(seg->x0), splashFloor(seg->x1), 0)) {
break;
}
}
} else if (seg->flags & splashXPathVert) {
y0 = splashFloor(segYMin);
if (y0 < yMin) {
y0 = yMin;
}
y1 = splashFloor(segYMax);
if (y1 > yMax) {
y1 = yMax;
}
x = splashFloor(seg->x0);
int count = eo || (seg->flags & splashXPathFlip) ? 1 : -1;
for (y = y0; y <= y1; ++y) {
if (!addIntersection(segYMin, segYMax, y, x, x, count)) {
break;
}
}
} else {
if (seg->x0 < seg->x1) {
segXMin = seg->x0;
segXMax = seg->x1;
} else {
segXMin = seg->x1;
segXMax = seg->x0;
}
y0 = splashFloor(segYMin);
if (y0 < yMin) {
y0 = yMin;
}
y1 = splashFloor(segYMax);
if (y1 > yMax) {
y1 = yMax;
}
int count = eo || (seg->flags & splashXPathFlip) ? 1 : -1;
// Calculate the projected intersection of the segment with the
// X-Axis.
SplashCoord xbase = seg->x0 - (seg->y0 * seg->dxdy);
xx0 = xbase + ((SplashCoord)y0) * seg->dxdy;
// the segment may not actually extend to the top and/or bottom edges
if (xx0 < segXMin) {
xx0 = segXMin;
} else if (xx0 > segXMax) {
xx0 = segXMax;
}
int x0 = splashFloor(xx0);
for (y = y0; y <= y1; ++y) {
xx1 = xbase + ((SplashCoord)(y + 1) * seg->dxdy);
if (xx1 < segXMin) {
xx1 = segXMin;
} else if (xx1 > segXMax) {
xx1 = segXMax;
}
int x1 = splashFloor(xx1);
if (!addIntersection(segYMin, segYMax, y, x0, x1, count)) {
break;
}
xx0 = xx1;
x0 = x1;
}
}
}
for (auto &line : allIntersections) {
std::ranges::sort(line, [](const SplashIntersect i0, const SplashIntersect i1) { return i0.x0 < i1.x0; });
}
}
inline bool SplashXPathScanner::addIntersection(double segYMin, double segYMax, int y, int x0, int x1, int count)
{
SplashIntersect intersect;
intersect.y = y;
if (x0 < x1) {
intersect.x0 = x0;
intersect.x1 = x1;
} else {
intersect.x0 = x1;
intersect.x1 = x0;
}
if (segYMin <= y && (SplashCoord)y < segYMax) {
intersect.count = count;
} else {
intersect.count = 0;
}
auto &line = allIntersections[y - yMin];
#ifndef USE_BOOST_HEADERS
if (line.empty()) {
line.reserve(4);
}
#endif
line.push_back(intersect);
return true;
}
void SplashXPathScanner::renderAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y, bool adjustVertLine) const
{
int xx0, xx1, xx, xxMin, xxMax, yy, yyMax, interCount;
size_t interIdx;
unsigned char mask;
SplashColorPtr p;
memset(aaBuf->getDataPtr(), 0, aaBuf->getRowSize() * aaBuf->getHeight());
xxMin = aaBuf->getWidth();
xxMax = -1;
if (yMin <= yMax) {
yy = 0;
yyMax = splashAASize - 1;
// clamp start and end position
if (yMin > splashAASize * y) {
yy = yMin - splashAASize * y;
}
if (yyMax + splashAASize * y > yMax) {
yyMax = yMax - splashAASize * y;
}
for (; yy <= yyMax; ++yy) {
const auto &line = allIntersections[splashAASize * y + yy - yMin];
interIdx = 0;
interCount = 0;
while (interIdx < line.size()) {
xx0 = line[interIdx].x0;
xx1 = line[interIdx].x1;
interCount += line[interIdx].count;
++interIdx;
while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (eo ? (interCount & 1) : (interCount != 0)))) {
if (line[interIdx].x1 > xx1) {
xx1 = line[interIdx].x1;
}
interCount += line[interIdx].count;
++interIdx;
}
if (xx0 < 0) {
xx0 = 0;
}
++xx1;
if (xx1 > aaBuf->getWidth()) {
xx1 = aaBuf->getWidth();
}
// set [xx0, xx1) to 1
if (xx0 < xx1) {
xx = xx0;
p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
if (xx & 7) {
mask = adjustVertLine ? 0xff : 0xff >> (xx & 7);
if (!adjustVertLine && (xx & ~7) == (xx1 & ~7)) {
mask &= (unsigned char)(0xff00 >> (xx1 & 7));
}
*p++ |= mask;
xx = (xx & ~7) + 8;
}
for (; xx + 7 < xx1; xx += 8) {
*p++ |= 0xff;
}
if (xx < xx1) {
*p |= adjustVertLine ? 0xff : (unsigned char)(0xff00 >> (xx1 & 7));
}
}
if (xx0 < xxMin) {
xxMin = xx0;
}
if (xx1 > xxMax) {
xxMax = xx1;
}
}
}
}
if (xxMin > xxMax) {
xxMin = xxMax;
}
*x0 = xxMin / splashAASize;
*x1 = (xxMax - 1) / splashAASize;
}
void SplashXPathScanner::clipAALine(SplashBitmap *aaBuf, const int *x0, const int *x1, int y) const
{
int xx0, xx1, xx, yy, yyMin, yyMax, interCount;
size_t interIdx;
unsigned char mask;
SplashColorPtr p;
yyMin = 0;
yyMax = splashAASize - 1;
// clamp start and end position
if (yMin > splashAASize * y) {
yyMin = yMin - splashAASize * y;
}
if (yyMax + splashAASize * y > yMax) {
yyMax = yMax - splashAASize * y;
}
for (yy = 0; yy < splashAASize; ++yy) {
xx = *x0 * splashAASize;
if (yy >= yyMin && yy <= yyMax) {
const int intersectionIndex = splashAASize * y + yy - yMin;
if (unlikely(intersectionIndex < 0 || (unsigned)intersectionIndex >= allIntersections.size())) {
break;
}
const auto &line = allIntersections[intersectionIndex];
interIdx = 0;
interCount = 0;
while (interIdx < line.size() && xx < (*x1 + 1) * splashAASize) {
xx0 = line[interIdx].x0;
xx1 = line[interIdx].x1;
interCount += line[interIdx].count;
++interIdx;
while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (eo ? (interCount & 1) : (interCount != 0)))) {
if (line[interIdx].x1 > xx1) {
xx1 = line[interIdx].x1;
}
interCount += line[interIdx].count;
++interIdx;
}
if (xx0 > aaBuf->getWidth()) {
xx0 = aaBuf->getWidth();
}
// set [xx, xx0) to 0
if (xx < xx0) {
p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
if (xx & 7) {
mask = (unsigned char)(0xff00 >> (xx & 7));
if ((xx & ~7) == (xx0 & ~7)) {
mask |= 0xff >> (xx0 & 7);
}
*p++ &= mask;
xx = (xx & ~7) + 8;
}
for (; xx + 7 < xx0; xx += 8) {
*p++ = 0x00;
}
if (xx < xx0) {
*p &= 0xff >> (xx0 & 7);
}
}
if (xx1 >= xx) {
xx = xx1 + 1;
}
}
}
xx0 = (*x1 + 1) * splashAASize;
if (xx0 > aaBuf->getWidth()) {
xx0 = aaBuf->getWidth();
}
// set [xx, xx0) to 0
if (xx < xx0 && xx >= 0) {
p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
if (xx & 7) {
mask = (unsigned char)(0xff00 >> (xx & 7));
if ((xx & ~7) == (xx0 & ~7)) {
mask &= 0xff >> (xx0 & 7);
}
*p++ &= mask;
xx = (xx & ~7) + 8;
}
for (; xx + 7 < xx0; xx += 8) {
*p++ = 0x00;
}
if (xx < xx0) {
*p &= 0xff >> (xx0 & 7);
}
}
}
}