Coverage Report

Created: 2026-09-28 08:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/splash/SplashXPathScanner.cc
Line
Count
Source
1
//========================================================================
2
//
3
// SplashXPathScanner.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) 2008, 2010, 2014, 2018, 2019, 2021, 2022, 2024-2026 Albert Astals Cid <aacid@kde.org>
15
// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
16
// Copyright (C) 2013, 2014, 2021 Thomas Freitag <Thomas.Freitag@alfa.de>
17
// Copyright (C) 2018, 2025 Stefan Brüns <stefan.bruens@rwth-aachen.de>
18
//
19
// To see a description of the changes please see the Changelog file that
20
// came with your tarball or type make ChangeLog if you are building from git
21
//
22
//========================================================================
23
24
#include <config.h>
25
26
#include <cstdlib>
27
#include <cstring>
28
#include <algorithm>
29
#include <limits>
30
#include "goo/GooLikely.h"
31
#include "SplashMath.h"
32
#include "SplashXPath.h"
33
#include "SplashBitmap.h"
34
#include "SplashXPathScanner.h"
35
36
//------------------------------------------------------------------------
37
38
//------------------------------------------------------------------------
39
// SplashXPathScanner
40
//------------------------------------------------------------------------
41
42
SplashXPathScanner::SplashXPathScanner(const SplashXPath &xPath, bool eoA, int clipYMin, int clipYMax) //
43
10.5M
    : eo(eoA)
44
10.5M
{
45
    // compute the bbox
46
10.5M
    if (xPath.length == 0) {
47
0
        return;
48
0
    }
49
10.5M
    if (clipYMin > clipYMax) {
50
61.8k
        return;
51
61.8k
    }
52
53
10.5M
    double xMaxFP = std::numeric_limits<double>::lowest();
54
10.5M
    double xMinFP = std::numeric_limits<double>::max();
55
10.5M
    double yMaxFP = std::numeric_limits<double>::lowest();
56
10.5M
    double yMinFP = std::numeric_limits<double>::max();
57
58
10.5M
    double clipYMinFP = clipYMin;
59
10.5M
    double clipYMaxFP = clipYMax + 1.0;
60
61
303M
    for (int i = 0; i < xPath.length; ++i) {
62
293M
        const SplashXPathSeg *seg = &xPath.segs[i];
63
293M
        if (unlikely(std::isnan(seg->x0) || std::isnan(seg->x1) || std::isnan(seg->y0) || std::isnan(seg->y1))) {
64
6.13k
            return;
65
6.13k
        }
66
67
293M
        const double segYMin = seg->y0;
68
293M
        const double segYMax = seg->y1;
69
293M
        if (segYMin >= clipYMaxFP) {
70
49.8M
            continue;
71
49.8M
        }
72
243M
        if (segYMax < clipYMinFP) {
73
71.6M
            continue;
74
71.6M
        }
75
76
171M
        if (segYMin < yMinFP) {
77
31.6M
            yMinFP = segYMin;
78
31.6M
        }
79
171M
        if (segYMax > yMaxFP) {
80
27.1M
            yMaxFP = segYMax;
81
27.1M
        }
82
171M
        if (seg->x0 < xMinFP) {
83
21.8M
            xMinFP = seg->x0;
84
21.8M
        }
85
171M
        if (seg->x0 > xMaxFP) {
86
22.0M
            xMaxFP = seg->x0;
87
22.0M
        }
88
171M
        if (seg->x1 < xMinFP) {
89
11.7M
            xMinFP = seg->x1;
90
11.7M
        }
91
171M
        if (seg->x1 > xMaxFP) {
92
14.7M
            xMaxFP = seg->x1;
93
14.7M
        }
94
171M
    }
95
10.5M
    if (yMinFP > yMaxFP) {
96
38.6k
        return;
97
38.6k
    }
98
99
10.4M
    xMin = splashFloor(xMinFP);
100
10.4M
    xMax = splashFloor(xMaxFP);
101
10.4M
    yMin = splashFloor(yMinFP);
102
10.4M
    yMax = splashFloor(yMaxFP);
103
10.4M
    if (clipYMin > yMin) {
104
185k
        yMin = clipYMin;
105
185k
    }
106
10.4M
    if (clipYMax < yMax) {
107
199k
        yMax = clipYMax;
108
199k
    }
109
110
10.4M
    if (yMin > yMax) {
111
        // This means the splashFloors overflowed/underflowed
112
171
        return;
113
171
    }
114
115
10.4M
    computeIntersections(xPath);
116
10.4M
}
117
118
10.5M
SplashXPathScanner::~SplashXPathScanner() = default;
119
120
void SplashXPathScanner::getBBoxAA(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA) const
121
6.62k
{
122
6.62k
    *xMinA = xMin / splashAASize;
123
6.62k
    *yMinA = yMin / splashAASize;
124
6.62k
    *xMaxA = xMax / splashAASize;
125
6.62k
    *yMaxA = yMax / splashAASize;
126
6.62k
}
127
128
bool SplashXPathScanner::test(int x, int y) const
129
612M
{
130
612M
    if (y < yMin || y > yMax) {
131
339M
        return false;
132
339M
    }
133
273M
    const auto &line = allIntersections[y - yMin];
134
273M
    int count = 0;
135
843M
    for (unsigned int i = 0; i < line.size() && line[i].x0 <= x; ++i) {
136
578M
        if (x <= line[i].x1) {
137
7.69M
            return true;
138
7.69M
        }
139
570M
        count += line[i].count;
140
570M
    }
141
265M
    return eo ? (count & 1) : (count != 0);
142
273M
}
143
144
bool SplashXPathScanner::testSpan(int x0, int x1, int y) const
145
46.5M
{
146
46.5M
    unsigned int i;
147
148
46.5M
    if (y < yMin || y > yMax) {
149
15.0M
        return false;
150
15.0M
    }
151
31.5M
    const auto &line = allIntersections[y - yMin];
152
31.5M
    int count = 0;
153
74.1M
    for (i = 0; i < line.size() && line[i].x1 < x0; ++i) {
154
42.6M
        count += line[i].count;
155
42.6M
    }
156
157
    // invariant: the subspan [x0,xx1] is inside the path
158
31.5M
    int xx1 = x0 - 1;
159
31.5M
    int eoMask = eo ? 0x1 : ~0;
160
58.7M
    while (xx1 < x1) {
161
32.4M
        if (i >= line.size()) {
162
2.99M
            return false;
163
2.99M
        }
164
29.4M
        if (line[i].x0 > xx1 + 1 && !(count & eoMask)) {
165
2.19M
            return false;
166
2.19M
        }
167
27.2M
        if (line[i].x1 > xx1) {
168
27.2M
            xx1 = line[i].x1;
169
27.2M
        }
170
27.2M
        count += line[i].count;
171
27.2M
        ++i;
172
27.2M
    }
173
174
26.3M
    return true;
175
31.5M
}
176
177
bool SplashXPathScanIterator::getNextSpan(int *x0, int *x1)
178
252M
{
179
252M
    int xx0, xx1;
180
181
252M
    if (interIdx >= line.size()) {
182
115M
        return false;
183
115M
    }
184
137M
    int eoMask = eo ? 0x1 : ~0;
185
137M
    xx0 = line[interIdx].x0;
186
137M
    xx1 = line[interIdx].x1;
187
137M
    interCount += line[interIdx].count;
188
137M
    ++interIdx;
189
232M
    while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (interCount & eoMask))) {
190
94.5M
        if (line[interIdx].x1 > xx1) {
191
83.7M
            xx1 = line[interIdx].x1;
192
83.7M
        }
193
94.5M
        interCount += line[interIdx].count;
194
94.5M
        ++interIdx;
195
94.5M
    }
196
137M
    *x0 = xx0;
197
137M
    *x1 = xx1;
198
137M
    return true;
199
252M
}
200
201
115M
SplashXPathScanIterator::SplashXPathScanIterator(const SplashXPathScanner &scanner, int y) : line((y < scanner.yMin || y > scanner.yMax) ? scanner.allIntersections[0] : scanner.allIntersections[y - scanner.yMin]), eo(scanner.eo)
202
115M
{
203
115M
    if (y < scanner.yMin || y > scanner.yMax) {
204
        // set index to line end
205
0
        interIdx = line.size();
206
0
    }
207
115M
}
208
209
void SplashXPathScanner::computeIntersections(const SplashXPath &xPath)
210
10.4M
{
211
    // build the list of all intersections
212
10.4M
    allIntersections.resize(yMax - yMin + 1);
213
214
10.4M
    const double clipYMinFP = yMin;
215
10.4M
    const double clipYMaxFP = yMax + 1.0;
216
217
299M
    for (int i = 0; i < xPath.length; ++i) {
218
288M
        const SplashXPathSeg *seg = &xPath.segs[i];
219
288M
        const double segYMin = seg->y0;
220
288M
        const double segYMax = seg->y1;
221
288M
        if (segYMin >= clipYMaxFP) {
222
47.3M
            continue;
223
47.3M
        }
224
241M
        if (segYMax < clipYMinFP) {
225
69.7M
            continue;
226
69.7M
        }
227
228
171M
        int y1 = splashFloor(segYMax);
229
171M
        int y0 = (seg->flags & splashXPathHoriz) ? y1 : splashFloor(segYMin);
230
231
171M
        if (y1 == y0) {
232
97.0M
            addIntersection(segYMin, y1, splashFloor(seg->x0), splashFloor(seg->x1), 0);
233
97.0M
        } else if (seg->flags & splashXPathVert) {
234
12.0M
            if (y0 < yMin) {
235
193k
                y0 = yMin;
236
193k
            }
237
12.0M
            if (y1 > yMax) {
238
941k
                y1 = yMax;
239
941k
            }
240
12.0M
            int x = splashFloor(seg->x0);
241
12.0M
            int count = (seg->flags & splashXPathFlipped) ? 1 : -1;
242
215M
            for (int y = y0; y <= y1; ++y) {
243
203M
                addIntersection(segYMin, y, x, x, count);
244
203M
            }
245
62.6M
        } else {
246
62.6M
            double segXMin, segXMax;
247
248
62.6M
            if (seg->x0 < seg->x1) {
249
29.0M
                segXMin = seg->x0;
250
29.0M
                segXMax = seg->x1;
251
33.6M
            } else {
252
33.6M
                segXMin = seg->x1;
253
33.6M
                segXMax = seg->x0;
254
33.6M
            }
255
            // Calculate the projected intersection of the segment with the
256
            // X-Axis.
257
62.6M
            double xbase = seg->x0 - (seg->y0 * seg->dxdy);
258
62.6M
            double xx0 = seg->x0;
259
62.6M
            if (y0 < yMin) {
260
1.28M
                y0 = yMin;
261
1.28M
                xx0 = xbase + static_cast<double>(y0) * seg->dxdy;
262
1.28M
            }
263
62.6M
            if (y1 > yMax) {
264
2.66M
                y1 = yMax;
265
2.66M
            }
266
62.6M
            int count = (seg->flags & splashXPathFlipped) ? 1 : -1;
267
            // the segment may not actually extend to the top and/or bottom edges
268
62.6M
            if (xx0 < segXMin) {
269
1.66k
                xx0 = segXMin;
270
62.6M
            } else if (xx0 > segXMax) {
271
132
                xx0 = segXMax;
272
132
            }
273
62.6M
            int x0 = splashFloor(xx0);
274
275
482M
            for (int y = y0; y <= y1; ++y) {
276
419M
                double xx1 = xbase + (static_cast<double>(y + 1) * seg->dxdy);
277
278
419M
                if (xx1 < segXMin) {
279
33.5M
                    xx1 = segXMin;
280
386M
                } else if (xx1 > segXMax) {
281
27.4M
                    xx1 = segXMax;
282
27.4M
                }
283
419M
                int x1 = splashFloor(xx1);
284
419M
                addIntersection(segYMin, y, x0, x1, count);
285
286
419M
                x0 = x1;
287
419M
            }
288
62.6M
        }
289
171M
    }
290
133M
    for (auto &line : allIntersections) {
291
1.65G
        std::ranges::sort(line, [](const SplashIntersect i0, const SplashIntersect i1) { return i0.x0 < i1.x0; });
292
133M
    }
293
10.4M
}
294
295
inline void SplashXPathScanner::addIntersection(double segYMin, int y, int x0, int x1, int count)
296
720M
{
297
720M
    SplashIntersect intersect;
298
720M
    if (x0 < x1) {
299
103M
        intersect.x0 = x0;
300
103M
        intersect.x1 = x1;
301
617M
    } else {
302
617M
        intersect.x0 = x1;
303
617M
        intersect.x1 = x0;
304
617M
    }
305
720M
    if (segYMin < y) {
306
550M
        intersect.count = count;
307
550M
    } else {
308
170M
        intersect.count = 0;
309
170M
    }
310
311
720M
    auto &line = allIntersections[y - yMin];
312
720M
    if (line.empty()) {
313
#if !USE_BOOST_HEADERS
314
        line.reserve(4);
315
#endif
316
132M
        line.push_back(intersect);
317
588M
    } else {
318
588M
        auto &last = line.back();
319
        // Check if last and new overlap/touch
320
588M
        if ((last.x1 + 1) < intersect.x0) {
321
121M
            line.push_back(intersect);
322
466M
        } else if (last.x0 > (intersect.x1 + 1)) {
323
129M
            line.push_back(intersect);
324
337M
        } else {
325
337M
            last.count += intersect.count;
326
337M
            last.x0 = last.x0 < intersect.x0 ? last.x0 : intersect.x0;
327
337M
            last.x1 = last.x1 > intersect.x1 ? last.x1 : intersect.x1;
328
337M
        }
329
588M
    }
330
720M
}
331
332
void SplashXPathScanner::renderAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y, bool adjustVertLine) const
333
49.2k
{
334
49.2k
    memset(aaBuf->getDataPtr(), 0, aaBuf->getRowSize() * aaBuf->getHeight());
335
49.2k
    int xxMin = aaBuf->getWidth();
336
49.2k
    int xxMax = -1;
337
49.2k
    if (yMin <= yMax) {
338
49.1k
        int yy = 0;
339
49.1k
        int yyMax = splashAASize - 1;
340
        // clamp start and end position
341
49.1k
        if (yMin > splashAASize * y) {
342
36
            yy = yMin - splashAASize * y;
343
36
        }
344
49.1k
        if (yyMax + splashAASize * y > yMax) {
345
248
            yyMax = yMax - splashAASize * y;
346
248
        }
347
348
49.1k
        int eoMask = eo ? 0x1 : ~0;
349
245k
        for (; yy <= yyMax; ++yy) {
350
195k
            const auto &line = allIntersections[splashAASize * y + yy - yMin];
351
195k
            size_t interIdx = 0;
352
195k
            int interCount = 0;
353
388k
            while (interIdx < line.size()) {
354
192k
                int xx0 = line[interIdx].x0;
355
192k
                int xx1 = line[interIdx].x1;
356
192k
                interCount += line[interIdx].count;
357
192k
                ++interIdx;
358
571k
                while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (interCount & eoMask))) {
359
378k
                    if (line[interIdx].x1 > xx1) {
360
369k
                        xx1 = line[interIdx].x1;
361
369k
                    }
362
378k
                    interCount += line[interIdx].count;
363
378k
                    ++interIdx;
364
378k
                }
365
192k
                if (xx0 < 0) {
366
1.86k
                    xx0 = 0;
367
1.86k
                }
368
192k
                ++xx1;
369
192k
                if (xx1 > aaBuf->getWidth()) {
370
126k
                    xx1 = aaBuf->getWidth();
371
126k
                }
372
                // set [xx0, xx1) to 1
373
192k
                if (xx0 < xx1) {
374
190k
                    int xx = xx0;
375
190k
                    SplashColorPtr p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
376
190k
                    if (xx & 7) {
377
1.61k
                        unsigned char mask = adjustVertLine ? 0xff : 0xff >> (xx & 7);
378
1.61k
                        if (!adjustVertLine && (xx & ~7) == (xx1 & ~7)) {
379
236
                            mask &= static_cast<unsigned char>(0xff00 >> (xx1 & 7));
380
236
                        }
381
1.61k
                        *p++ |= mask;
382
1.61k
                        xx = (xx & ~7) + 8;
383
1.61k
                    }
384
9.10M
                    for (; xx + 7 < xx1; xx += 8) {
385
8.91M
                        *p++ |= 0xff;
386
8.91M
                    }
387
190k
                    if (xx < xx1) {
388
70.6k
                        *p |= adjustVertLine ? 0xff : static_cast<unsigned char>(0xff00 >> (xx1 & 7));
389
70.6k
                    }
390
190k
                }
391
192k
                if (xx0 < xxMin) {
392
47.5k
                    xxMin = xx0;
393
47.5k
                }
394
192k
                if (xx1 > xxMax) {
395
48.5k
                    xxMax = xx1;
396
48.5k
                }
397
192k
            }
398
195k
        }
399
49.1k
    }
400
49.2k
    if (xxMin > xxMax) {
401
1.69k
        xxMin = xxMax;
402
1.69k
    }
403
49.2k
    *x0 = xxMin / splashAASize;
404
49.2k
    *x1 = (xxMax - 1) / splashAASize;
405
49.2k
}
406
407
void SplashXPathScanner::clipAALine(SplashBitmap *aaBuf, const int *x0, const int *x1, int y) const
408
39.1k
{
409
39.1k
    int yyMin = 0;
410
39.1k
    int yyMax = splashAASize - 1;
411
    // clamp start and end position
412
39.1k
    if (yMin > splashAASize * y) {
413
190
        yyMin = yMin - splashAASize * y;
414
190
    }
415
39.1k
    if (yyMax + splashAASize * y > yMax) {
416
33.9k
        yyMax = yMax - splashAASize * y;
417
33.9k
    }
418
39.1k
    int eoMask = eo ? 0x1 : ~0;
419
195k
    for (int yy = 0; yy < splashAASize; ++yy) {
420
156k
        int xx = *x0 * splashAASize;
421
156k
        if (yy >= yyMin && yy <= yyMax) {
422
25.0k
            const int intersectionIndex = splashAASize * y + yy - yMin;
423
25.0k
            if (unlikely(intersectionIndex < 0 || (unsigned)intersectionIndex >= allIntersections.size())) {
424
0
                break;
425
0
            }
426
25.0k
            const auto &line = allIntersections[intersectionIndex];
427
25.0k
            size_t interIdx = 0;
428
25.0k
            int interCount = 0;
429
50.0k
            while (interIdx < line.size() && xx < (*x1 + 1) * splashAASize) {
430
25.0k
                int xx0 = line[interIdx].x0;
431
25.0k
                int xx1 = line[interIdx].x1;
432
25.0k
                interCount += line[interIdx].count;
433
25.0k
                ++interIdx;
434
85.6k
                while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (interCount & eoMask))) {
435
60.6k
                    if (line[interIdx].x1 > xx1) {
436
39.2k
                        xx1 = line[interIdx].x1;
437
39.2k
                    }
438
60.6k
                    interCount += line[interIdx].count;
439
60.6k
                    ++interIdx;
440
60.6k
                }
441
25.0k
                if (xx0 > aaBuf->getWidth()) {
442
0
                    xx0 = aaBuf->getWidth();
443
0
                }
444
                // set [xx, xx0) to 0
445
25.0k
                if (xx < xx0) {
446
0
                    SplashColorPtr p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
447
0
                    if (xx & 7) {
448
0
                        auto mask = static_cast<unsigned char>(0xff00 >> (xx & 7));
449
0
                        if ((xx & ~7) == (xx0 & ~7)) {
450
0
                            mask |= 0xff >> (xx0 & 7);
451
0
                        }
452
0
                        *p++ &= mask;
453
0
                        xx = (xx & ~7) + 8;
454
0
                    }
455
0
                    for (; xx + 7 < xx0; xx += 8) {
456
0
                        *p++ = 0x00;
457
0
                    }
458
0
                    if (xx < xx0) {
459
0
                        *p &= 0xff >> (xx0 & 7);
460
0
                    }
461
0
                }
462
25.0k
                if (xx1 >= xx) {
463
25.0k
                    xx = xx1 + 1;
464
25.0k
                }
465
25.0k
            }
466
25.0k
        }
467
156k
        int xx0 = (*x1 + 1) * splashAASize;
468
156k
        if (xx0 > aaBuf->getWidth()) {
469
0
            xx0 = aaBuf->getWidth();
470
0
        }
471
        // set [xx, xx0) to 0
472
156k
        if (xx < xx0 && xx >= 0) {
473
137k
            SplashColorPtr p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
474
137k
            if (xx & 7) {
475
5.29k
                auto mask = static_cast<unsigned char>(0xff00 >> (xx & 7));
476
5.29k
                if ((xx & ~7) == (xx0 & ~7)) {
477
5.23k
                    mask &= 0xff >> (xx0 & 7);
478
5.23k
                }
479
5.29k
                *p++ &= mask;
480
5.29k
                xx = (xx & ~7) + 8;
481
5.29k
            }
482
440k
            for (; xx + 7 < xx0; xx += 8) {
483
303k
                *p++ = 0x00;
484
303k
            }
485
137k
            if (xx < xx0) {
486
131k
                *p &= 0xff >> (xx0 & 7);
487
131k
            }
488
137k
        }
489
156k
    }
490
39.1k
}