Coverage Report

Created: 2026-08-11 07:29

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