Coverage Report

Created: 2026-07-30 07:17

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
0
    : eo(eoA)
44
0
{
45
    // compute the bbox
46
0
    if (xPath.length == 0) {
47
0
        return;
48
0
    }
49
0
    if (clipYMin > clipYMax) {
50
0
        return;
51
0
    }
52
53
0
    double xMaxFP = std::numeric_limits<double>::lowest();
54
0
    double xMinFP = std::numeric_limits<double>::max();
55
0
    double yMaxFP = std::numeric_limits<double>::lowest();
56
0
    double yMinFP = std::numeric_limits<double>::max();
57
58
0
    double clipYMinFP = clipYMin;
59
0
    double clipYMaxFP = clipYMax + 1.0;
60
61
0
    for (int i = 0; i < xPath.length; ++i) {
62
0
        const SplashXPathSeg *seg = &xPath.segs[i];
63
0
        if (unlikely(std::isnan(seg->x0) || std::isnan(seg->x1) || std::isnan(seg->y0) || std::isnan(seg->y1))) {
64
0
            return;
65
0
        }
66
67
0
        const double segYMin = seg->y0;
68
0
        const double segYMax = seg->y1;
69
0
        if (segYMin >= clipYMaxFP) {
70
0
            continue;
71
0
        }
72
0
        if (segYMax < clipYMinFP) {
73
0
            continue;
74
0
        }
75
76
0
        if (segYMin < yMinFP) {
77
0
            yMinFP = segYMin;
78
0
        }
79
0
        if (segYMax > yMaxFP) {
80
0
            yMaxFP = segYMax;
81
0
        }
82
0
        if (seg->x0 < xMinFP) {
83
0
            xMinFP = seg->x0;
84
0
        }
85
0
        if (seg->x0 > xMaxFP) {
86
0
            xMaxFP = seg->x0;
87
0
        }
88
0
        if (seg->x1 < xMinFP) {
89
0
            xMinFP = seg->x1;
90
0
        }
91
0
        if (seg->x1 > xMaxFP) {
92
0
            xMaxFP = seg->x1;
93
0
        }
94
0
    }
95
0
    if (yMinFP > yMaxFP) {
96
0
        return;
97
0
    }
98
99
0
    xMin = splashFloor(xMinFP);
100
0
    xMax = splashFloor(xMaxFP);
101
0
    yMin = splashFloor(yMinFP);
102
0
    yMax = splashFloor(yMaxFP);
103
0
    if (clipYMin > yMin) {
104
0
        yMin = clipYMin;
105
0
    }
106
0
    if (clipYMax < yMax) {
107
0
        yMax = clipYMax;
108
0
    }
109
110
0
    if (yMin > yMax) {
111
        // This means the splashFloors overflowed/underflowed
112
0
        return;
113
0
    }
114
115
0
    computeIntersections(xPath);
116
0
}
117
118
0
SplashXPathScanner::~SplashXPathScanner() = default;
119
120
void SplashXPathScanner::getBBoxAA(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA) const
121
0
{
122
0
    *xMinA = xMin / splashAASize;
123
0
    *yMinA = yMin / splashAASize;
124
0
    *xMaxA = xMax / splashAASize;
125
0
    *yMaxA = yMax / splashAASize;
126
0
}
127
128
bool SplashXPathScanner::test(int x, int y) const
129
0
{
130
0
    if (y < yMin || y > yMax) {
131
0
        return false;
132
0
    }
133
0
    const auto &line = allIntersections[y - yMin];
134
0
    int count = 0;
135
0
    for (unsigned int i = 0; i < line.size() && line[i].x0 <= x; ++i) {
136
0
        if (x <= line[i].x1) {
137
0
            return true;
138
0
        }
139
0
        count += line[i].count;
140
0
    }
141
0
    return eo ? (count & 1) : (count != 0);
142
0
}
143
144
bool SplashXPathScanner::testSpan(int x0, int x1, int y) const
145
0
{
146
0
    unsigned int i;
147
148
0
    if (y < yMin || y > yMax) {
149
0
        return false;
150
0
    }
151
0
    const auto &line = allIntersections[y - yMin];
152
0
    int count = 0;
153
0
    for (i = 0; i < line.size() && line[i].x1 < x0; ++i) {
154
0
        count += line[i].count;
155
0
    }
156
157
    // invariant: the subspan [x0,xx1] is inside the path
158
0
    int xx1 = x0 - 1;
159
0
    int eoMask = eo ? 0x1 : ~0;
160
0
    while (xx1 < x1) {
161
0
        if (i >= line.size()) {
162
0
            return false;
163
0
        }
164
0
        if (line[i].x0 > xx1 + 1 && !(count & eoMask)) {
165
0
            return false;
166
0
        }
167
0
        if (line[i].x1 > xx1) {
168
0
            xx1 = line[i].x1;
169
0
        }
170
0
        count += line[i].count;
171
0
        ++i;
172
0
    }
173
174
0
    return true;
175
0
}
176
177
bool SplashXPathScanIterator::getNextSpan(int *x0, int *x1)
178
0
{
179
0
    int xx0, xx1;
180
181
0
    if (interIdx >= line.size()) {
182
0
        return false;
183
0
    }
184
0
    int eoMask = eo ? 0x1 : ~0;
185
0
    xx0 = line[interIdx].x0;
186
0
    xx1 = line[interIdx].x1;
187
0
    interCount += line[interIdx].count;
188
0
    ++interIdx;
189
0
    while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (interCount & eoMask))) {
190
0
        if (line[interIdx].x1 > xx1) {
191
0
            xx1 = line[interIdx].x1;
192
0
        }
193
0
        interCount += line[interIdx].count;
194
0
        ++interIdx;
195
0
    }
196
0
    *x0 = xx0;
197
0
    *x1 = xx1;
198
0
    return true;
199
0
}
200
201
0
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
0
{
203
0
    if (y < scanner.yMin || y > scanner.yMax) {
204
        // set index to line end
205
0
        interIdx = line.size();
206
0
    }
207
0
}
208
209
void SplashXPathScanner::computeIntersections(const SplashXPath &xPath)
210
0
{
211
    // build the list of all intersections
212
0
    allIntersections.resize(yMax - yMin + 1);
213
214
0
    const double clipYMinFP = yMin;
215
0
    const double clipYMaxFP = yMax + 1.0;
216
217
0
    for (int i = 0; i < xPath.length; ++i) {
218
0
        const SplashXPathSeg *seg = &xPath.segs[i];
219
0
        const double segYMin = seg->y0;
220
0
        const double segYMax = seg->y1;
221
0
        if (segYMin >= clipYMaxFP) {
222
0
            continue;
223
0
        }
224
0
        if (segYMax < clipYMinFP) {
225
0
            continue;
226
0
        }
227
228
0
        int y1 = splashFloor(segYMax);
229
0
        int y0 = (seg->flags & splashXPathHoriz) ? y1 : splashFloor(segYMin);
230
231
0
        if (y1 == y0) {
232
0
            addIntersection(segYMin, y1, splashFloor(seg->x0), splashFloor(seg->x1), 0);
233
0
        } else if (seg->flags & splashXPathVert) {
234
0
            if (y0 < yMin) {
235
0
                y0 = yMin;
236
0
            }
237
0
            if (y1 > yMax) {
238
0
                y1 = yMax;
239
0
            }
240
0
            int x = splashFloor(seg->x0);
241
0
            int count = (seg->flags & splashXPathFlipped) ? 1 : -1;
242
0
            for (int y = y0; y <= y1; ++y) {
243
0
                addIntersection(segYMin, y, x, x, count);
244
0
            }
245
0
        } else {
246
0
            double segXMin, segXMax;
247
248
0
            if (seg->x0 < seg->x1) {
249
0
                segXMin = seg->x0;
250
0
                segXMax = seg->x1;
251
0
            } else {
252
0
                segXMin = seg->x1;
253
0
                segXMax = seg->x0;
254
0
            }
255
            // Calculate the projected intersection of the segment with the
256
            // X-Axis.
257
0
            double xbase = seg->x0 - (seg->y0 * seg->dxdy);
258
0
            double xx0 = seg->x0;
259
0
            if (y0 < yMin) {
260
0
                y0 = yMin;
261
0
                xx0 = xbase + static_cast<double>(y0) * seg->dxdy;
262
0
            }
263
0
            if (y1 > yMax) {
264
0
                y1 = yMax;
265
0
            }
266
0
            int count = (seg->flags & splashXPathFlipped) ? 1 : -1;
267
            // the segment may not actually extend to the top and/or bottom edges
268
0
            if (xx0 < segXMin) {
269
0
                xx0 = segXMin;
270
0
            } else if (xx0 > segXMax) {
271
0
                xx0 = segXMax;
272
0
            }
273
0
            int x0 = splashFloor(xx0);
274
275
0
            for (int y = y0; y <= y1; ++y) {
276
0
                double xx1 = xbase + (static_cast<double>(y + 1) * seg->dxdy);
277
278
0
                if (xx1 < segXMin) {
279
0
                    xx1 = segXMin;
280
0
                } else if (xx1 > segXMax) {
281
0
                    xx1 = segXMax;
282
0
                }
283
0
                int x1 = splashFloor(xx1);
284
0
                addIntersection(segYMin, y, x0, x1, count);
285
286
0
                x0 = x1;
287
0
            }
288
0
        }
289
0
    }
290
0
    for (auto &line : allIntersections) {
291
0
        std::ranges::sort(line, [](const SplashIntersect i0, const SplashIntersect i1) { return i0.x0 < i1.x0; });
292
0
    }
293
0
}
294
295
inline void SplashXPathScanner::addIntersection(double segYMin, int y, int x0, int x1, int count)
296
0
{
297
0
    SplashIntersect intersect;
298
0
    if (x0 < x1) {
299
0
        intersect.x0 = x0;
300
0
        intersect.x1 = x1;
301
0
    } else {
302
0
        intersect.x0 = x1;
303
0
        intersect.x1 = x0;
304
0
    }
305
0
    if (segYMin < y) {
306
0
        intersect.count = count;
307
0
    } else {
308
0
        intersect.count = 0;
309
0
    }
310
311
0
    auto &line = allIntersections[y - yMin];
312
0
    if (line.empty()) {
313
0
#if !USE_BOOST_HEADERS
314
0
        line.reserve(4);
315
0
#endif
316
0
        line.push_back(intersect);
317
0
    } else {
318
0
        auto &last = line.back();
319
        // Check if last and new overlap/touch
320
0
        if ((last.x1 + 1) < intersect.x0) {
321
0
            line.push_back(intersect);
322
0
        } else if (last.x0 > (intersect.x1 + 1)) {
323
0
            line.push_back(intersect);
324
0
        } else {
325
0
            last.count += intersect.count;
326
0
            last.x0 = last.x0 < intersect.x0 ? last.x0 : intersect.x0;
327
0
            last.x1 = last.x1 > intersect.x1 ? last.x1 : intersect.x1;
328
0
        }
329
0
    }
330
0
}
331
332
void SplashXPathScanner::renderAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y, bool adjustVertLine) const
333
0
{
334
0
    memset(aaBuf->getDataPtr(), 0, aaBuf->getRowSize() * aaBuf->getHeight());
335
0
    int xxMin = aaBuf->getWidth();
336
0
    int xxMax = -1;
337
0
    if (yMin <= yMax) {
338
0
        int yy = 0;
339
0
        int yyMax = splashAASize - 1;
340
        // clamp start and end position
341
0
        if (yMin > splashAASize * y) {
342
0
            yy = yMin - splashAASize * y;
343
0
        }
344
0
        if (yyMax + splashAASize * y > yMax) {
345
0
            yyMax = yMax - splashAASize * y;
346
0
        }
347
348
0
        int eoMask = eo ? 0x1 : ~0;
349
0
        for (; yy <= yyMax; ++yy) {
350
0
            const auto &line = allIntersections[splashAASize * y + yy - yMin];
351
0
            size_t interIdx = 0;
352
0
            int interCount = 0;
353
0
            while (interIdx < line.size()) {
354
0
                int xx0 = line[interIdx].x0;
355
0
                int xx1 = line[interIdx].x1;
356
0
                interCount += line[interIdx].count;
357
0
                ++interIdx;
358
0
                while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (interCount & eoMask))) {
359
0
                    if (line[interIdx].x1 > xx1) {
360
0
                        xx1 = line[interIdx].x1;
361
0
                    }
362
0
                    interCount += line[interIdx].count;
363
0
                    ++interIdx;
364
0
                }
365
0
                if (xx0 < 0) {
366
0
                    xx0 = 0;
367
0
                }
368
0
                ++xx1;
369
0
                if (xx1 > aaBuf->getWidth()) {
370
0
                    xx1 = aaBuf->getWidth();
371
0
                }
372
                // set [xx0, xx1) to 1
373
0
                if (xx0 < xx1) {
374
0
                    int xx = xx0;
375
0
                    SplashColorPtr p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
376
0
                    if (xx & 7) {
377
0
                        unsigned char mask = adjustVertLine ? 0xff : 0xff >> (xx & 7);
378
0
                        if (!adjustVertLine && (xx & ~7) == (xx1 & ~7)) {
379
0
                            mask &= static_cast<unsigned char>(0xff00 >> (xx1 & 7));
380
0
                        }
381
0
                        *p++ |= mask;
382
0
                        xx = (xx & ~7) + 8;
383
0
                    }
384
0
                    for (; xx + 7 < xx1; xx += 8) {
385
0
                        *p++ |= 0xff;
386
0
                    }
387
0
                    if (xx < xx1) {
388
0
                        *p |= adjustVertLine ? 0xff : static_cast<unsigned char>(0xff00 >> (xx1 & 7));
389
0
                    }
390
0
                }
391
0
                if (xx0 < xxMin) {
392
0
                    xxMin = xx0;
393
0
                }
394
0
                if (xx1 > xxMax) {
395
0
                    xxMax = xx1;
396
0
                }
397
0
            }
398
0
        }
399
0
    }
400
0
    if (xxMin > xxMax) {
401
0
        xxMin = xxMax;
402
0
    }
403
0
    *x0 = xxMin / splashAASize;
404
0
    *x1 = (xxMax - 1) / splashAASize;
405
0
}
406
407
void SplashXPathScanner::clipAALine(SplashBitmap *aaBuf, const int *x0, const int *x1, int y) const
408
0
{
409
0
    int yyMin = 0;
410
0
    int yyMax = splashAASize - 1;
411
    // clamp start and end position
412
0
    if (yMin > splashAASize * y) {
413
0
        yyMin = yMin - splashAASize * y;
414
0
    }
415
0
    if (yyMax + splashAASize * y > yMax) {
416
0
        yyMax = yMax - splashAASize * y;
417
0
    }
418
0
    int eoMask = eo ? 0x1 : ~0;
419
0
    for (int yy = 0; yy < splashAASize; ++yy) {
420
0
        int xx = *x0 * splashAASize;
421
0
        if (yy >= yyMin && yy <= yyMax) {
422
0
            const int intersectionIndex = splashAASize * y + yy - yMin;
423
0
            if (unlikely(intersectionIndex < 0 || (unsigned)intersectionIndex >= allIntersections.size())) {
424
0
                break;
425
0
            }
426
0
            const auto &line = allIntersections[intersectionIndex];
427
0
            size_t interIdx = 0;
428
0
            int interCount = 0;
429
0
            while (interIdx < line.size() && xx < (*x1 + 1) * splashAASize) {
430
0
                int xx0 = line[interIdx].x0;
431
0
                int xx1 = line[interIdx].x1;
432
0
                interCount += line[interIdx].count;
433
0
                ++interIdx;
434
0
                while (interIdx < line.size() && (line[interIdx].x0 <= xx1 || (interCount & eoMask))) {
435
0
                    if (line[interIdx].x1 > xx1) {
436
0
                        xx1 = line[interIdx].x1;
437
0
                    }
438
0
                    interCount += line[interIdx].count;
439
0
                    ++interIdx;
440
0
                }
441
0
                if (xx0 > aaBuf->getWidth()) {
442
0
                    xx0 = aaBuf->getWidth();
443
0
                }
444
                // set [xx, xx0) to 0
445
0
                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
0
                if (xx1 >= xx) {
463
0
                    xx = xx1 + 1;
464
0
                }
465
0
            }
466
0
        }
467
0
        int xx0 = (*x1 + 1) * splashAASize;
468
0
        if (xx0 > aaBuf->getWidth()) {
469
0
            xx0 = aaBuf->getWidth();
470
0
        }
471
        // set [xx, xx0) to 0
472
0
        if (xx < xx0 && xx >= 0) {
473
0
            SplashColorPtr p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
474
0
            if (xx & 7) {
475
0
                auto mask = static_cast<unsigned char>(0xff00 >> (xx & 7));
476
0
                if ((xx & ~7) == (xx0 & ~7)) {
477
0
                    mask &= 0xff >> (xx0 & 7);
478
0
                }
479
0
                *p++ &= mask;
480
0
                xx = (xx & ~7) + 8;
481
0
            }
482
0
            for (; xx + 7 < xx0; xx += 8) {
483
0
                *p++ = 0x00;
484
0
            }
485
0
            if (xx < xx0) {
486
0
                *p &= 0xff >> (xx0 & 7);
487
0
            }
488
0
        }
489
0
    }
490
0
}