Coverage Report

Created: 2026-07-30 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/splash/SplashClip.cc
Line
Count
Source
1
//========================================================================
2
//
3
// SplashClip.cc
4
//
5
//========================================================================
6
7
//========================================================================
8
//
9
// Modified under the Poppler project - http://poppler.freedesktop.org
10
//
11
// All changes made under the Poppler project to this file are licensed
12
// under GPL version 2 or later
13
//
14
// Copyright (C) 2010, 2021, 2025, 2026 Albert Astals Cid <aacid@kde.org>
15
// Copyright (C) 2013, 2021 Thomas Freitag <Thomas.Freitag@alfa.de>
16
// Copyright (C) 2019, 2025 Stefan BrĂ¼ns <stefan.bruens@rwth-aachen.de>
17
//
18
// To see a description of the changes please see the Changelog file that
19
// came with your tarball or type make ChangeLog if you are building from git
20
//
21
//========================================================================
22
23
#include <config.h>
24
25
#include <algorithm>
26
#include "SplashErrorCodes.h"
27
#include "SplashMath.h"
28
#include "SplashPath.h"
29
#include "SplashXPath.h"
30
#include "SplashXPathScanner.h"
31
#include "SplashBitmap.h"
32
#include "SplashClip.h"
33
34
//------------------------------------------------------------------------
35
// SplashClip
36
//------------------------------------------------------------------------
37
38
SplashClip::SplashClip(double x0, double y0, double x1, double y1, bool antialiasA)
39
0
{
40
0
    antialias = antialiasA;
41
0
    if (x0 < x1) {
42
0
        xMin = x0;
43
0
        xMax = x1;
44
0
    } else {
45
0
        xMin = x1;
46
0
        xMax = x0;
47
0
    }
48
0
    if (y0 < y1) {
49
0
        yMin = y0;
50
0
        yMax = y1;
51
0
    } else {
52
0
        yMin = y1;
53
0
        yMax = y0;
54
0
    }
55
0
    xMinI = splashFloor(xMin);
56
0
    yMinI = splashFloor(yMin);
57
0
    xMaxI = splashCeil(xMax) - 1;
58
0
    yMaxI = splashCeil(yMax) - 1;
59
0
}
60
61
SplashClip::SplashClip(const SplashClip *clip, PrivateTag /*unused*/)
62
0
{
63
0
    antialias = clip->antialias;
64
0
    xMin = clip->xMin;
65
0
    yMin = clip->yMin;
66
0
    xMax = clip->xMax;
67
0
    yMax = clip->yMax;
68
0
    xMinI = clip->xMinI;
69
0
    yMinI = clip->yMinI;
70
0
    xMaxI = clip->xMaxI;
71
0
    yMaxI = clip->yMaxI;
72
0
    scanners = clip->scanners;
73
0
}
74
75
void SplashClip::resetToRect(double x0, double y0, double x1, double y1)
76
0
{
77
0
    scanners = {};
78
79
0
    if (x0 < x1) {
80
0
        xMin = x0;
81
0
        xMax = x1;
82
0
    } else {
83
0
        xMin = x1;
84
0
        xMax = x0;
85
0
    }
86
0
    if (y0 < y1) {
87
0
        yMin = y0;
88
0
        yMax = y1;
89
0
    } else {
90
0
        yMin = y1;
91
0
        yMax = y0;
92
0
    }
93
0
    xMinI = splashFloor(xMin);
94
0
    yMinI = splashFloor(yMin);
95
0
    xMaxI = splashCeil(xMax) - 1;
96
0
    yMaxI = splashCeil(yMax) - 1;
97
0
}
98
99
SplashError SplashClip::clipToRect(double x0, double y0, double x1, double y1)
100
0
{
101
0
    if (x0 < x1) {
102
0
        if (x0 > xMin) {
103
0
            xMin = x0;
104
0
            xMinI = splashFloor(xMin);
105
0
        }
106
0
        if (x1 < xMax) {
107
0
            xMax = x1;
108
0
            xMaxI = splashCeil(xMax) - 1;
109
0
        }
110
0
    } else {
111
0
        if (x1 > xMin) {
112
0
            xMin = x1;
113
0
            xMinI = splashFloor(xMin);
114
0
        }
115
0
        if (x0 < xMax) {
116
0
            xMax = x0;
117
0
            xMaxI = splashCeil(xMax) - 1;
118
0
        }
119
0
    }
120
0
    if (y0 < y1) {
121
0
        if (y0 > yMin) {
122
0
            yMin = y0;
123
0
            yMinI = splashFloor(yMin);
124
0
        }
125
0
        if (y1 < yMax) {
126
0
            yMax = y1;
127
0
            yMaxI = splashCeil(yMax) - 1;
128
0
        }
129
0
    } else {
130
0
        if (y1 > yMin) {
131
0
            yMin = y1;
132
0
            yMinI = splashFloor(yMin);
133
0
        }
134
0
        if (y0 < yMax) {
135
0
            yMax = y0;
136
0
            yMaxI = splashCeil(yMax) - 1;
137
0
        }
138
0
    }
139
0
    return SplashError::NoError;
140
0
}
141
142
namespace {
143
// returns true if the 4 consecutive segments form a axis aligned rectangle
144
// first and third segment must be the vertical segments
145
constexpr bool isRect(const SplashXPathSeg &a, const SplashXPathSeg &b, const SplashXPathSeg &c, const SplashXPathSeg &d)
146
0
{
147
    // Check if segment a and c are vertical, and b and d are horizontal
148
0
    if ((a.x0 != a.x1) || (b.y0 != b.y1) || (c.x0 != c.x1) || (d.y0 != d.y1)) {
149
0
        return false;
150
0
    }
151
    // Check if x coordinates match
152
0
    if ((a.x1 != b.x0) || (b.x1 != c.x0) || (c.x1 != d.x0) || (d.x1 != a.x0)) {
153
0
        return false;
154
0
    }
155
0
    if ((a.y0 != c.y0) || (a.y1 != c.y1)) {
156
0
        return false;
157
0
    }
158
0
    if ((a.y0 == b.y0) && (a.y1 == d.y0)) {
159
0
        return true;
160
0
    }
161
0
    if ((a.y0 == d.y0) && (a.y1 == b.y0)) {
162
0
        return true;
163
0
    }
164
0
    return false;
165
0
}
166
// 4 valid cases - two orientations, start on left or right
167
static_assert(isRect({ .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 1.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
168
                     { .x0 = 2.0, .y0 = 0.0, .x1 = 0.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
169
static_assert(isRect({ .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 2.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
170
                     { .x0 = 2.0, .y0 = 1.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }));
171
static_assert(isRect({ .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 0.0, .x1 = 0.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
172
                     { .x0 = 0.0, .y0 = 1.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }));
173
static_assert(isRect({ .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 1.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
174
                     { .x0 = 0.0, .y0 = 0.0, .x1 = 2.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
175
// 4 invalid cases, one segment point not closing
176
static_assert(!isRect({ .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 3.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 1.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
177
                      { .x0 = 0.0, .y0 = 0.0, .x1 = 2.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
178
static_assert(!isRect({ .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 3.0, .y0 = 1.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
179
                      { .x0 = 0.0, .y0 = 0.0, .x1 = 2.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
180
static_assert(!isRect({ .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 1.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 3.0, .dxdy = 0.0, .flags = 0 },
181
                      { .x0 = 0.0, .y0 = 0.0, .x1 = 2.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
182
static_assert(!isRect({ .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 1.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
183
                      { .x0 = 0.0, .y0 = 0.0, .x1 = 3.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
184
// invalid case, closed, but left segment not vertical
185
static_assert(!isRect({ .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 1.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 1.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
186
                      { .x0 = 1.0, .y0 = 0.0, .x1 = 2.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
187
// invalid case, all horizontal/vertical, but horizontal segments coincident
188
static_assert(!isRect({ .x0 = 0.0, .y0 = 0.0, .x1 = 0.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 0.0, .y0 = 0.0, .x1 = 2.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }, { .x0 = 2.0, .y0 = 0.0, .x1 = 2.0, .y1 = 1.0, .dxdy = 0.0, .flags = 0 },
189
                      { .x0 = 2.0, .y0 = 0.0, .x1 = 0.0, .y1 = 0.0, .dxdy = 0.0, .flags = 0 }));
190
}
191
192
SplashError SplashClip::clipToPath(const SplashPath &path, const std::array<double, 6> &matrix, double flatness, bool eo)
193
0
{
194
0
    int yMinAA, yMaxAA;
195
196
0
    SplashXPath xPath(path, matrix, flatness, true);
197
198
    // check for an empty path
199
0
    if (xPath.length == 0) {
200
0
        xMax = xMin - 1;
201
0
        yMax = yMin - 1;
202
0
        xMaxI = splashCeil(xMax) - 1;
203
0
        yMaxI = splashCeil(yMax) - 1;
204
205
        // check for an axis aligned rectangle
206
0
    } else if (xPath.length == 4 && isRect(xPath.segs[0], xPath.segs[1], xPath.segs[2], xPath.segs[3])) {
207
0
        clipToRect(xPath.segs[0].x0, xPath.segs[0].y0, xPath.segs[2].x0, xPath.segs[2].y1);
208
0
    } else if (xPath.length == 4 && isRect(xPath.segs[1], xPath.segs[2], xPath.segs[3], xPath.segs[0])) {
209
0
        clipToRect(xPath.segs[1].x0, xPath.segs[1].y0, xPath.segs[3].x0, xPath.segs[3].y1);
210
211
0
    } else {
212
0
        if (antialias) {
213
0
            xPath.aaScale();
214
0
            yMinAA = yMinI * splashAASize;
215
0
            yMaxAA = (yMaxI + 1) * splashAASize - 1;
216
0
        } else {
217
0
            yMinAA = yMinI;
218
0
            yMaxAA = yMaxI;
219
0
        }
220
0
        scanners.emplace_back(std::make_shared<SplashXPathScanner>(xPath, eo, yMinAA, yMaxAA));
221
0
    }
222
223
0
    return SplashError::NoError;
224
0
}
225
226
SplashClipResult SplashClip::testRect(int rectXMin, int rectYMin, int rectXMax, int rectYMax) const
227
0
{
228
    // This tests the rectangle:
229
    //     x = [rectXMin, rectXMax + 1)    (note: rect coords are ints)
230
    //     y = [rectYMin, rectYMax + 1)
231
    // against the clipping region:
232
    //     x = [xMin, xMax)                (note: clipping coords are fp)
233
    //     y = [yMin, yMax)
234
0
    if (static_cast<double>(rectXMax + 1) <= xMin || static_cast<double>(rectXMin) >= xMax || static_cast<double>(rectYMax + 1) <= yMin || static_cast<double>(rectYMin) >= yMax) {
235
0
        return splashClipAllOutside;
236
0
    }
237
0
    if (static_cast<double>(rectXMin) >= xMin && static_cast<double>(rectXMax + 1) <= xMax && static_cast<double>(rectYMin) >= yMin && static_cast<double>(rectYMax + 1) <= yMax && scanners.empty()) {
238
0
        return splashClipAllInside;
239
0
    }
240
0
    return splashClipPartial;
241
0
}
242
243
SplashClipResult SplashClip::testSpan(int spanXMin, int spanXMax, int spanY)
244
0
{
245
    // This tests the rectangle:
246
    //     x = [spanXMin, spanXMax + 1)    (note: span coords are ints)
247
    //     y = [spanY, spanY + 1)
248
    // against the clipping region:
249
    //     x = [xMin, xMax)                (note: clipping coords are fp)
250
    //     y = [yMin, yMax)
251
0
    if (static_cast<double>(spanXMax + 1) <= xMin || static_cast<double>(spanXMin) >= xMax || static_cast<double>(spanY + 1) <= yMin || static_cast<double>(spanY) >= yMax) {
252
0
        return splashClipAllOutside;
253
0
    }
254
0
    if (static_cast<double>(spanXMin) < xMin || static_cast<double>(spanXMax + 1) > xMax || static_cast<double>(spanY) < yMin || static_cast<double>(spanY + 1) > yMax) {
255
0
        return splashClipPartial;
256
0
    }
257
0
    if (antialias) {
258
0
        for (const auto &scanner : scanners) {
259
0
            if (!scanner->testSpan(spanXMin * splashAASize, spanXMax * splashAASize + (splashAASize - 1), spanY * splashAASize)) {
260
0
                return splashClipPartial;
261
0
            }
262
0
        }
263
0
    } else {
264
0
        for (const auto &scanner : scanners) {
265
0
            if (!scanner->testSpan(spanXMin, spanXMax, spanY)) {
266
0
                return splashClipPartial;
267
0
            }
268
0
        }
269
0
    }
270
0
    return splashClipAllInside;
271
0
}
272
273
void SplashClip::clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y, bool adjustVertLine)
274
0
{
275
0
    int xx0, xx1, xx, yy;
276
0
    SplashColorPtr p;
277
278
    // zero out pixels with x < xMin
279
0
    xx0 = *x0 * splashAASize;
280
0
    xx1 = splashFloor(xMin * splashAASize);
281
0
    if (xx1 > aaBuf->getWidth()) {
282
0
        xx1 = aaBuf->getWidth();
283
0
    }
284
0
    if (xx0 < xx1) {
285
0
        xx0 &= ~7;
286
0
        for (yy = 0; yy < splashAASize; ++yy) {
287
0
            p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
288
0
            for (xx = xx0; xx + 7 < xx1; xx += 8) {
289
0
                *p++ = 0;
290
0
            }
291
0
            if (xx < xx1 && !adjustVertLine) {
292
0
                *p &= 0xff >> (xx1 & 7);
293
0
            }
294
0
        }
295
0
        *x0 = splashFloor(xMin);
296
0
    }
297
298
    // zero out pixels with x > xMax
299
0
    xx0 = splashFloor(xMax * splashAASize) + 1;
300
0
    if (xx0 < 0) {
301
0
        xx0 = 0;
302
0
    }
303
0
    xx1 = (*x1 + 1) * splashAASize;
304
0
    if (xx0 < xx1 && !adjustVertLine) {
305
0
        for (yy = 0; yy < splashAASize; ++yy) {
306
0
            p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
307
0
            xx = xx0;
308
0
            if (xx & 7) {
309
0
                *p &= 0xff00 >> (xx & 7);
310
0
                xx = (xx & ~7) + 8;
311
0
                ++p;
312
0
            }
313
0
            for (; xx < xx1; xx += 8) {
314
0
                *p++ = 0;
315
0
            }
316
0
        }
317
0
        *x1 = splashFloor(xMax);
318
0
    }
319
320
    // check the paths
321
0
    for (const auto &scanner : scanners) {
322
0
        scanner->clipAALine(aaBuf, x0, x1, y);
323
0
    }
324
0
    if (*x0 > *x1) {
325
0
        *x0 = *x1;
326
0
    }
327
0
    if (*x0 < 0) {
328
0
        *x0 = 0;
329
0
    }
330
0
    if ((*x0 >> 1) >= aaBuf->getRowSize()) {
331
0
        xx0 = *x0;
332
0
        *x0 = (aaBuf->getRowSize() - 1) << 1;
333
0
        if (xx0 & 1) {
334
0
            *x0 = *x0 + 1;
335
0
        }
336
0
    }
337
0
    if (*x1 < *x0) {
338
0
        *x1 = *x0;
339
0
    }
340
0
    if ((*x1 >> 1) >= aaBuf->getRowSize()) {
341
0
        xx0 = *x1;
342
0
        *x1 = (aaBuf->getRowSize() - 1) << 1;
343
0
        if (xx0 & 1) {
344
0
            *x1 = *x1 + 1;
345
0
        }
346
0
    }
347
0
}
348
349
bool SplashClip::testClipPaths(int x, int y) const
350
0
{
351
0
    if (antialias) {
352
0
        x *= splashAASize;
353
0
        y *= splashAASize;
354
0
    }
355
356
0
    auto testXY = [x, y](const auto &scanner) -> bool { //
357
0
        return scanner->test(x, y);
358
0
    };
359
360
0
    return std::ranges::all_of(scanners, testXY);
361
0
}