Coverage Report

Created: 2026-03-31 07:41

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(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1, bool antialiasA)
39
413k
{
40
413k
    antialias = antialiasA;
41
413k
    if (x0 < x1) {
42
413k
        xMin = x0;
43
413k
        xMax = x1;
44
413k
    } else {
45
0
        xMin = x1;
46
0
        xMax = x0;
47
0
    }
48
413k
    if (y0 < y1) {
49
413k
        yMin = y0;
50
413k
        yMax = y1;
51
413k
    } else {
52
0
        yMin = y1;
53
0
        yMax = y0;
54
0
    }
55
413k
    xMinI = splashFloor(xMin);
56
413k
    yMinI = splashFloor(yMin);
57
413k
    xMaxI = splashCeil(xMax) - 1;
58
413k
    yMaxI = splashCeil(yMax) - 1;
59
413k
}
60
61
SplashClip::SplashClip(const SplashClip *clip, PrivateTag /*unused*/)
62
4.32M
{
63
4.32M
    antialias = clip->antialias;
64
4.32M
    xMin = clip->xMin;
65
4.32M
    yMin = clip->yMin;
66
4.32M
    xMax = clip->xMax;
67
4.32M
    yMax = clip->yMax;
68
4.32M
    xMinI = clip->xMinI;
69
4.32M
    yMinI = clip->yMinI;
70
4.32M
    xMaxI = clip->xMaxI;
71
4.32M
    yMaxI = clip->yMaxI;
72
4.32M
    scanners = clip->scanners;
73
4.32M
}
74
75
void SplashClip::resetToRect(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord 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(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1)
100
852k
{
101
852k
    if (x0 < x1) {
102
41.3k
        if (x0 > xMin) {
103
17.5k
            xMin = x0;
104
17.5k
            xMinI = splashFloor(xMin);
105
17.5k
        }
106
41.3k
        if (x1 < xMax) {
107
19.5k
            xMax = x1;
108
19.5k
            xMaxI = splashCeil(xMax) - 1;
109
19.5k
        }
110
811k
    } else {
111
811k
        if (x1 > xMin) {
112
427k
            xMin = x1;
113
427k
            xMinI = splashFloor(xMin);
114
427k
        }
115
811k
        if (x0 < xMax) {
116
434k
            xMax = x0;
117
434k
            xMaxI = splashCeil(xMax) - 1;
118
434k
        }
119
811k
    }
120
852k
    if (y0 < y1) {
121
842k
        if (y0 > yMin) {
122
424k
            yMin = y0;
123
424k
            yMinI = splashFloor(yMin);
124
424k
        }
125
842k
        if (y1 < yMax) {
126
410k
            yMax = y1;
127
410k
            yMaxI = splashCeil(yMax) - 1;
128
410k
        }
129
842k
    } else {
130
10.4k
        if (y1 > yMin) {
131
6.84k
            yMin = y1;
132
6.84k
            yMinI = splashFloor(yMin);
133
6.84k
        }
134
10.4k
        if (y0 < yMax) {
135
445
            yMax = y0;
136
445
            yMaxI = splashCeil(yMax) - 1;
137
445
        }
138
10.4k
    }
139
852k
    return SplashError::NoError;
140
852k
}
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
2.15M
{
147
    // Check if segment a and c are vertical, and b and d are horizontal
148
2.15M
    if ((a.x0 != a.x1) || (b.y0 != b.y1) || (c.x0 != c.x1) || (d.y0 != d.y1)) {
149
1.29M
        return false;
150
1.29M
    }
151
    // Check if x coordinates match
152
852k
    if ((a.x1 != b.x0) || (b.x1 != c.x0) || (c.x1 != d.x0) || (d.x1 != a.x0)) {
153
74
        return false;
154
74
    }
155
852k
    if ((a.y0 != c.y0) || (a.y1 != c.y1)) {
156
12
        return false;
157
12
    }
158
852k
    if ((a.y0 == b.y0) && (a.y1 == d.y0)) {
159
720k
        return true;
160
720k
    }
161
132k
    if ((a.y0 == d.y0) && (a.y1 == b.y0)) {
162
132k
        return true;
163
132k
    }
164
27
    return false;
165
132k
}
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<SplashCoord, 6> &matrix, SplashCoord flatness, bool eo)
193
1.35M
{
194
1.35M
    int yMinAA, yMaxAA;
195
196
1.35M
    SplashXPath xPath(path, matrix, flatness, true);
197
198
    // check for an empty path
199
1.35M
    if (xPath.length == 0) {
200
4.68k
        xMax = xMin - 1;
201
4.68k
        yMax = yMin - 1;
202
4.68k
        xMaxI = splashCeil(xMax) - 1;
203
4.68k
        yMaxI = splashCeil(yMax) - 1;
204
205
        // check for an axis aligned rectangle
206
1.34M
    } else if (xPath.length == 4 && isRect(xPath.segs[0], xPath.segs[1], xPath.segs[2], xPath.segs[3])) {
207
27.1k
        clipToRect(xPath.segs[0].x0, xPath.segs[0].y0, xPath.segs[2].x0, xPath.segs[2].y1);
208
1.32M
    } else if (xPath.length == 4 && isRect(xPath.segs[1], xPath.segs[2], xPath.segs[3], xPath.segs[0])) {
209
825k
        clipToRect(xPath.segs[1].x0, xPath.segs[1].y0, xPath.segs[3].x0, xPath.segs[3].y1);
210
211
825k
    } else {
212
494k
        if (antialias) {
213
139
            xPath.aaScale();
214
139
            yMinAA = yMinI * splashAASize;
215
139
            yMaxAA = (yMaxI + 1) * splashAASize - 1;
216
494k
        } else {
217
494k
            yMinAA = yMinI;
218
494k
            yMaxAA = yMaxI;
219
494k
        }
220
494k
        scanners.emplace_back(std::make_shared<SplashXPathScanner>(xPath, eo, yMinAA, yMaxAA));
221
494k
    }
222
223
1.35M
    return SplashError::NoError;
224
1.35M
}
225
226
SplashClipResult SplashClip::testRect(int rectXMin, int rectYMin, int rectXMax, int rectYMax) const
227
161M
{
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
161M
    if ((SplashCoord)(rectXMax + 1) <= xMin || (SplashCoord)rectXMin >= xMax || (SplashCoord)(rectYMax + 1) <= yMin || (SplashCoord)rectYMin >= yMax) {
235
109M
        return splashClipAllOutside;
236
109M
    }
237
51.6M
    if ((SplashCoord)rectXMin >= xMin && (SplashCoord)(rectXMax + 1) <= xMax && (SplashCoord)rectYMin >= yMin && (SplashCoord)(rectYMax + 1) <= yMax && scanners.empty()) {
238
33.2M
        return splashClipAllInside;
239
33.2M
    }
240
18.4M
    return splashClipPartial;
241
51.6M
}
242
243
SplashClipResult SplashClip::testSpan(int spanXMin, int spanXMax, int spanY)
244
123M
{
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
123M
    if ((SplashCoord)(spanXMax + 1) <= xMin || (SplashCoord)spanXMin >= xMax || (SplashCoord)(spanY + 1) <= yMin || (SplashCoord)spanY >= yMax) {
252
46.5M
        return splashClipAllOutside;
253
46.5M
    }
254
76.9M
    if ((SplashCoord)spanXMin < xMin || (SplashCoord)(spanXMax + 1) > xMax || (SplashCoord)spanY < yMin || (SplashCoord)(spanY + 1) > yMax) {
255
6.83M
        return splashClipPartial;
256
6.83M
    }
257
70.1M
    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
70.1M
    } else {
264
70.1M
        for (const auto &scanner : scanners) {
265
42.0M
            if (!scanner->testSpan(spanXMin, spanXMax, spanY)) {
266
24.6M
                return splashClipPartial;
267
24.6M
            }
268
42.0M
        }
269
70.1M
    }
270
45.4M
    return splashClipAllInside;
271
70.1M
}
272
273
void SplashClip::clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y, bool adjustVertLine)
274
55.6k
{
275
55.6k
    int xx0, xx1, xx, yy;
276
55.6k
    SplashColorPtr p;
277
278
    // zero out pixels with x < xMin
279
55.6k
    xx0 = *x0 * splashAASize;
280
55.6k
    xx1 = splashFloor(xMin * splashAASize);
281
55.6k
    if (xx1 > aaBuf->getWidth()) {
282
0
        xx1 = aaBuf->getWidth();
283
0
    }
284
55.6k
    if (xx0 < xx1) {
285
3.56k
        xx0 &= ~7;
286
17.8k
        for (yy = 0; yy < splashAASize; ++yy) {
287
14.2k
            p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
288
61.4k
            for (xx = xx0; xx + 7 < xx1; xx += 8) {
289
47.2k
                *p++ = 0;
290
47.2k
            }
291
14.2k
            if (xx < xx1 && !adjustVertLine) {
292
2.68k
                *p &= 0xff >> (xx1 & 7);
293
2.68k
            }
294
14.2k
        }
295
3.56k
        *x0 = splashFloor(xMin);
296
3.56k
    }
297
298
    // zero out pixels with x > xMax
299
55.6k
    xx0 = splashFloor(xMax * splashAASize) + 1;
300
55.6k
    if (xx0 < 0) {
301
0
        xx0 = 0;
302
0
    }
303
55.6k
    xx1 = (*x1 + 1) * splashAASize;
304
55.6k
    if (xx0 < xx1 && !adjustVertLine) {
305
152k
        for (yy = 0; yy < splashAASize; ++yy) {
306
121k
            p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
307
121k
            xx = xx0;
308
121k
            if (xx & 7) {
309
121k
                *p &= 0xff00 >> (xx & 7);
310
121k
                xx = (xx & ~7) + 8;
311
121k
                ++p;
312
121k
            }
313
325k
            for (; xx < xx1; xx += 8) {
314
203k
                *p++ = 0;
315
203k
            }
316
121k
        }
317
30.4k
        *x1 = splashFloor(xMax);
318
30.4k
    }
319
320
    // check the paths
321
55.6k
    for (const auto &scanner : scanners) {
322
35.7k
        scanner->clipAALine(aaBuf, x0, x1, y);
323
35.7k
    }
324
55.6k
    if (*x0 > *x1) {
325
60
        *x0 = *x1;
326
60
    }
327
55.6k
    if (*x0 < 0) {
328
0
        *x0 = 0;
329
0
    }
330
55.6k
    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
55.6k
    if (*x1 < *x0) {
338
0
        *x1 = *x0;
339
0
    }
340
55.6k
    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
55.6k
}
348
349
bool SplashClip::testClipPaths(int x, int y) const
350
2.89G
{
351
2.89G
    if (antialias) {
352
52.3k
        x *= splashAASize;
353
52.3k
        y *= splashAASize;
354
52.3k
    }
355
356
2.89G
    auto testXY = [x, y](const auto &scanner) -> bool { //
357
689M
        return scanner->test(x, y);
358
689M
    };
359
360
2.89G
    return std::ranges::all_of(scanners, testXY);
361
2.89G
}