Coverage Report

Created: 2026-07-30 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/splash/SplashXPath.cc
Line
Count
Source
1
//========================================================================
2
//
3
// SplashXPath.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 Paweł Wiejacha <pawel.wiejacha@gmail.com>
15
// Copyright (C) 2010, 2011, 2018, 2019, 2021, 2025 Albert Astals Cid <aacid@kde.org>
16
// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
17
// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
18
// Copyright (C) 2025 Stefan Brüns <stefan.bruens@rwth-aachen.de>
19
//
20
// To see a description of the changes please see the Changelog file that
21
// came with your tarball or type make ChangeLog if you are building from git
22
//
23
//========================================================================
24
25
#include <config.h>
26
27
#include "goo/gmem.h"
28
#include "goo/GooLikely.h"
29
#include "SplashMath.h"
30
#include "SplashPath.h"
31
#include "SplashXPath.h"
32
33
//------------------------------------------------------------------------
34
35
struct SplashXPathPoint
36
{
37
    double x, y;
38
};
39
40
struct SplashXPathAdjust
41
{
42
    int firstPt, lastPt; // range of points
43
    bool vert; // vertical or horizontal hint
44
    double x0a, x0b, // hint boundaries
45
            xma, xmb, x1a, x1b;
46
    double x0, x1, xm; // adjusted coordinates
47
};
48
49
//------------------------------------------------------------------------
50
51
// Transform a point from user space to device space.
52
inline void SplashXPath::transform(const std::array<double, 6> &matrix, double xi, double yi, double *xo, double *yo)
53
0
{
54
    //                          [ m[0] m[1] 0 ]
55
    // [xo yo 1] = [xi yi 1] *  [ m[2] m[3] 0 ]
56
    //                          [ m[4] m[5] 1 ]
57
0
    *xo = xi * matrix[0] + yi * matrix[2] + matrix[4];
58
0
    *yo = xi * matrix[1] + yi * matrix[3] + matrix[5];
59
0
}
60
61
//------------------------------------------------------------------------
62
// SplashXPath
63
//------------------------------------------------------------------------
64
65
SplashXPath::SplashXPath(const SplashPath &path, const std::array<double, 6> &matrix, double flatness, bool closeSubpaths, bool adjustLines, int linePosI)
66
0
{
67
0
    SplashPathHint *hint;
68
0
    SplashXPathPoint *pts;
69
0
    SplashXPathAdjust *adjusts, *adjust;
70
0
    double x0, y0, x1, y1, x2, y2, x3, y3, xsp, ysp;
71
0
    double adj0, adj1;
72
0
    int curSubpath, i, j;
73
74
    // transform the points
75
0
    pts = static_cast<SplashXPathPoint *>(gmallocn(path.length, sizeof(SplashXPathPoint)));
76
0
    for (i = 0; i < path.length; ++i) {
77
0
        transform(matrix, path.pts[i].x, path.pts[i].y, &pts[i].x, &pts[i].y);
78
0
    }
79
80
    // set up the stroke adjustment hints
81
0
    if (path.hints) {
82
0
        adjusts = static_cast<SplashXPathAdjust *>(gmallocn_checkoverflow(path.hintsLength, sizeof(SplashXPathAdjust)));
83
0
        if (adjusts) {
84
0
            for (i = 0; i < path.hintsLength; ++i) {
85
0
                hint = &path.hints[i];
86
0
                if (hint->ctrl0 + 1 >= path.length || hint->ctrl1 + 1 >= path.length) {
87
0
                    gfree(adjusts);
88
0
                    adjusts = nullptr;
89
0
                    break;
90
0
                }
91
0
                x0 = pts[hint->ctrl0].x;
92
0
                y0 = pts[hint->ctrl0].y;
93
0
                x1 = pts[hint->ctrl0 + 1].x;
94
0
                y1 = pts[hint->ctrl0 + 1].y;
95
0
                x2 = pts[hint->ctrl1].x;
96
0
                y2 = pts[hint->ctrl1].y;
97
0
                x3 = pts[hint->ctrl1 + 1].x;
98
0
                y3 = pts[hint->ctrl1 + 1].y;
99
0
                if (x0 == x1 && x2 == x3) {
100
0
                    adjusts[i].vert = true;
101
0
                    adj0 = x0;
102
0
                    adj1 = x2;
103
0
                } else if (y0 == y1 && y2 == y3) {
104
0
                    adjusts[i].vert = false;
105
0
                    adj0 = y0;
106
0
                    adj1 = y2;
107
0
                } else {
108
0
                    gfree(adjusts);
109
0
                    adjusts = nullptr;
110
0
                    break;
111
0
                }
112
0
                if (adj0 > adj1) {
113
0
                    x0 = adj0;
114
0
                    adj0 = adj1;
115
0
                    adj1 = x0;
116
0
                }
117
0
                adjusts[i].x0a = adj0 - 0.01;
118
0
                adjusts[i].x0b = adj0 + 0.01;
119
0
                adjusts[i].xma = 0.5 * (adj0 + adj1) - 0.01;
120
0
                adjusts[i].xmb = 0.5 * (adj0 + adj1) + 0.01;
121
0
                adjusts[i].x1a = adj1 - 0.01;
122
0
                adjusts[i].x1b = adj1 + 0.01;
123
                // rounding both edge coordinates can result in lines of
124
                // different widths (e.g., adj=10.1, adj1=11.3 --> x0=10, x1=11;
125
                // adj0=10.4, adj1=11.6 --> x0=10, x1=12), but it has the
126
                // benefit of making adjacent strokes/fills line up without any
127
                // gaps between them
128
0
                x0 = splashRound(adj0);
129
0
                x1 = splashRound(adj1);
130
0
                if (x1 == x0) {
131
0
                    if (adjustLines) {
132
                        // the adjustment moves thin lines (clip rectangle with
133
                        // empty width or height) out of clip area, here we need
134
                        // a special adjustment:
135
0
                        x0 = linePosI;
136
0
                        x1 = x0 + 1;
137
0
                    } else {
138
0
                        x1 = x1 + 1;
139
0
                    }
140
0
                }
141
0
                adjusts[i].x0 = x0;
142
0
                adjusts[i].x1 = x1 - 0.01;
143
0
                adjusts[i].xm = 0.5 * (adjusts[i].x0 + adjusts[i].x1);
144
0
                adjusts[i].firstPt = hint->firstPt;
145
0
                adjusts[i].lastPt = hint->lastPt;
146
0
            }
147
0
        }
148
149
0
    } else {
150
0
        adjusts = nullptr;
151
0
    }
152
153
    // perform stroke adjustment
154
0
    if (adjusts) {
155
0
        for (i = 0, adjust = adjusts; i < path.hintsLength; ++i, ++adjust) {
156
0
            for (j = adjust->firstPt; j <= adjust->lastPt; ++j) {
157
0
                strokeAdjust(adjust, &pts[j].x, &pts[j].y);
158
0
            }
159
0
        }
160
0
        gfree(adjusts);
161
0
    }
162
163
0
    segs = nullptr;
164
0
    length = size = 0;
165
166
0
    x0 = y0 = xsp = ysp = 0; // make gcc happy
167
0
    adj0 = adj1 = 0; // make gcc happy
168
0
    curSubpath = 0;
169
0
    i = 0;
170
    // reserve space for segments, rough estimate
171
0
    grow(path.length * 2);
172
173
0
    while (i < path.length) {
174
175
        // first point in subpath - skip it
176
0
        if (path.flags[i] & splashPathFirst) {
177
0
            x0 = pts[i].x;
178
0
            y0 = pts[i].y;
179
0
            xsp = x0;
180
0
            ysp = y0;
181
0
            curSubpath = i;
182
0
            ++i;
183
184
0
        } else {
185
186
            // curve segment
187
0
            if (path.flags[i] & splashPathCurve) {
188
0
                x1 = pts[i].x;
189
0
                y1 = pts[i].y;
190
0
                x2 = pts[i + 1].x;
191
0
                y2 = pts[i + 1].y;
192
0
                x3 = pts[i + 2].x;
193
0
                y3 = pts[i + 2].y;
194
0
                addCurve(x0, y0, x1, y1, x2, y2, x3, y3, flatness);
195
0
                x0 = x3;
196
0
                y0 = y3;
197
0
                i += 3;
198
199
                // line segment
200
0
            } else {
201
0
                x1 = pts[i].x;
202
0
                y1 = pts[i].y;
203
0
                addSegment(x0, y0, x1, y1);
204
0
                x0 = x1;
205
0
                y0 = y1;
206
0
                ++i;
207
0
            }
208
209
            // close a subpath
210
0
            if (closeSubpaths && (path.flags[i - 1] & splashPathLast) && (pts[i - 1].x != pts[curSubpath].x || pts[i - 1].y != pts[curSubpath].y)) {
211
0
                addSegment(x0, y0, xsp, ysp);
212
0
            }
213
0
        }
214
0
    }
215
216
0
    gfree(pts);
217
0
}
218
219
// Apply the stroke adjust hints to point <pt>: (*<xp>, *<yp>).
220
void SplashXPath::strokeAdjust(SplashXPathAdjust *adjust, double *xp, double *yp)
221
0
{
222
0
    double x, y;
223
224
0
    if (adjust->vert) {
225
0
        x = *xp;
226
0
        if (x > adjust->x0a && x < adjust->x0b) {
227
0
            *xp = adjust->x0;
228
0
        } else if (x > adjust->xma && x < adjust->xmb) {
229
0
            *xp = adjust->xm;
230
0
        } else if (x > adjust->x1a && x < adjust->x1b) {
231
0
            *xp = adjust->x1;
232
0
        }
233
0
    } else {
234
0
        y = *yp;
235
0
        if (y > adjust->x0a && y < adjust->x0b) {
236
0
            *yp = adjust->x0;
237
0
        } else if (y > adjust->xma && y < adjust->xmb) {
238
0
            *yp = adjust->xm;
239
0
        } else if (y > adjust->x1a && y < adjust->x1b) {
240
0
            *yp = adjust->x1;
241
0
        }
242
0
    }
243
0
}
244
245
SplashXPath::~SplashXPath()
246
0
{
247
0
    gfree(segs);
248
0
}
249
250
// Add space for <nSegs> more segments
251
void SplashXPath::grow(int nSegs)
252
0
{
253
0
    if (length + nSegs > size) {
254
0
        if (size == 0) {
255
0
            size = 32;
256
0
        }
257
0
        while (size < length + nSegs) {
258
0
            size *= 2;
259
0
        }
260
0
        segs = static_cast<SplashXPathSeg *>(greallocn_checkoverflow(segs, size, sizeof(SplashXPathSeg)));
261
0
        if (unlikely(!segs)) {
262
0
            length = 0;
263
0
            size = 0;
264
0
        }
265
0
    }
266
0
}
267
268
void SplashXPath::addCurve(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double flatness)
269
0
{
270
0
    if (!curveData) {
271
        // allocate on first use
272
0
        curveData = std::make_unique<CurveData>();
273
0
    }
274
0
    double *cx = curveData->cx.data();
275
0
    double *cy = curveData->cy.data();
276
0
    int *cNext = curveData->cNext.data();
277
278
0
    double xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh;
279
0
    double yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh;
280
0
    double dx, dy, mx, my, d1, d2, flatness2;
281
0
    int p1, p2, p3;
282
283
0
    flatness2 = flatness * flatness;
284
285
    // initial segment
286
0
    p1 = 0;
287
0
    p2 = splashMaxCurveSplits;
288
289
0
    *(cx + p1 * 3 + 0) = x0;
290
0
    *(cx + p1 * 3 + 1) = x1;
291
0
    *(cx + p1 * 3 + 2) = x2;
292
0
    *(cx + p2 * 3 + 0) = x3;
293
294
0
    *(cy + p1 * 3 + 0) = y0;
295
0
    *(cy + p1 * 3 + 1) = y1;
296
0
    *(cy + p1 * 3 + 2) = y2;
297
0
    *(cy + p2 * 3 + 0) = y3;
298
299
0
    *(cNext + p1) = p2;
300
301
0
    while (p1 < splashMaxCurveSplits) {
302
303
        // get the next segment
304
0
        xl0 = *(cx + p1 * 3 + 0);
305
0
        xx1 = *(cx + p1 * 3 + 1);
306
0
        xx2 = *(cx + p1 * 3 + 2);
307
308
0
        yl0 = *(cy + p1 * 3 + 0);
309
0
        yy1 = *(cy + p1 * 3 + 1);
310
0
        yy2 = *(cy + p1 * 3 + 2);
311
312
0
        p2 = *(cNext + p1);
313
314
0
        xr3 = *(cx + p2 * 3 + 0);
315
0
        yr3 = *(cy + p2 * 3 + 0);
316
317
        // compute the distances from the control points to the
318
        // midpoint of the straight line (this is a bit of a hack, but
319
        // it's much faster than computing the actual distances to the
320
        // line)
321
0
        mx = (xl0 + xr3) * 0.5;
322
0
        my = (yl0 + yr3) * 0.5;
323
0
        dx = xx1 - mx;
324
0
        dy = yy1 - my;
325
0
        d1 = dx * dx + dy * dy;
326
0
        dx = xx2 - mx;
327
0
        dy = yy2 - my;
328
0
        d2 = dx * dx + dy * dy;
329
330
        // if the curve is flat enough, or no more subdivisions are
331
        // allowed, add the straight line segment
332
0
        if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) {
333
0
            addSegment(xl0, yl0, xr3, yr3);
334
0
            p1 = p2;
335
336
            // otherwise, subdivide the curve
337
0
        } else {
338
0
            xl1 = (xl0 + xx1) * 0.5;
339
0
            yl1 = (yl0 + yy1) * 0.5;
340
0
            xh = (xx1 + xx2) * 0.5;
341
0
            yh = (yy1 + yy2) * 0.5;
342
0
            xl2 = (xl1 + xh) * 0.5;
343
0
            yl2 = (yl1 + yh) * 0.5;
344
0
            xr2 = (xx2 + xr3) * 0.5;
345
0
            yr2 = (yy2 + yr3) * 0.5;
346
0
            xr1 = (xh + xr2) * 0.5;
347
0
            yr1 = (yh + yr2) * 0.5;
348
0
            xr0 = (xl2 + xr1) * 0.5;
349
0
            yr0 = (yl2 + yr1) * 0.5;
350
            // add the new subdivision points
351
0
            p3 = (p1 + p2) / 2;
352
353
0
            *(cx + p1 * 3 + 1) = xl1;
354
0
            *(cx + p1 * 3 + 2) = xl2;
355
356
0
            *(cy + p1 * 3 + 1) = yl1;
357
0
            *(cy + p1 * 3 + 2) = yl2;
358
359
0
            *(cNext + p1) = p3;
360
361
0
            *(cx + p3 * 3 + 0) = xr0;
362
0
            *(cx + p3 * 3 + 1) = xr1;
363
0
            *(cx + p3 * 3 + 2) = xr2;
364
365
0
            *(cy + p3 * 3 + 0) = yr0;
366
0
            *(cy + p3 * 3 + 1) = yr1;
367
0
            *(cy + p3 * 3 + 2) = yr2;
368
369
0
            *(cNext + p3) = p2;
370
0
        }
371
0
    }
372
0
}
373
374
void SplashXPath::addSegment(double x0, double y0, double x1, double y1)
375
0
{
376
0
    grow(1);
377
0
    if (unlikely(!segs)) {
378
0
        return;
379
0
    }
380
0
    segs[length].x0 = x0;
381
0
    segs[length].y0 = y0;
382
0
    segs[length].x1 = x1;
383
0
    segs[length].y1 = y1;
384
0
    segs[length].flags = 0;
385
0
    if (y1 == y0) {
386
0
        segs[length].dxdy = 0;
387
0
        segs[length].flags |= splashXPathHoriz;
388
0
        if (x1 == x0) {
389
0
            segs[length].flags |= splashXPathVert;
390
0
        }
391
0
        ++length;
392
0
        return;
393
0
    }
394
395
0
    if (x1 == x0) {
396
0
        segs[length].dxdy = 0;
397
0
        segs[length].flags |= splashXPathVert;
398
0
    } else {
399
0
        segs[length].dxdy = (x1 - x0) / (y1 - y0);
400
0
    }
401
0
    if (y0 > y1) {
402
0
        segs[length].y1 = y0;
403
0
        segs[length].y0 = y1;
404
0
        segs[length].x1 = x0;
405
0
        segs[length].x0 = x1;
406
0
        segs[length].flags |= splashXPathFlipped;
407
0
    }
408
0
    ++length;
409
0
}
410
411
void SplashXPath::aaScale()
412
0
{
413
0
    SplashXPathSeg *seg;
414
0
    int i;
415
416
0
    for (i = 0, seg = segs; i < length; ++i, ++seg) {
417
0
        seg->x0 *= splashAASize;
418
0
        seg->y0 *= splashAASize;
419
0
        seg->x1 *= splashAASize;
420
0
        seg->y1 *= splashAASize;
421
0
    }
422
0
}