/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 | } |