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