1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtGui module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qtriangulatingstroker_p.h"
41#include <qmath.h>
42
43QT_BEGIN_NAMESPACE
44
45#define CURVE_FLATNESS Q_PI / 8
46
47
48
49
50void QTriangulatingStroker::endCapOrJoinClosed(const qreal *start, const qreal *cur,
51 bool implicitClose, bool endsAtStart)
52{
53 Q_ASSERT(start);
54 if (endsAtStart) {
55 join(pts: start + 2);
56 } else if (implicitClose) {
57 join(pts: start);
58 lineTo(pts: start);
59 join(pts: start+2);
60 } else {
61 endCap(pts: cur);
62 }
63 int count = m_vertices.size();
64
65 // Copy the (x, y) values because QDataBuffer::add(const float& t)
66 // may resize the buffer, which will leave t pointing at the
67 // previous buffer's memory region if we don't copy first.
68 float x = m_vertices.at(i: count-2);
69 float y = m_vertices.at(i: count-1);
70 m_vertices.add(t: x);
71 m_vertices.add(t: y);
72}
73
74static inline void skipDuplicatePoints(const qreal **pts, const qreal *endPts)
75{
76 while ((*pts + 2) < endPts && float((*pts)[0]) == float((*pts)[2])
77 && float((*pts)[1]) == float((*pts)[3]))
78 {
79 *pts += 2;
80 }
81}
82
83void QTriangulatingStroker::process(const QVectorPath &path, const QPen &pen, const QRectF &, QPainter::RenderHints hints)
84{
85 const qreal *pts = path.points();
86 const QPainterPath::ElementType *types = path.elements();
87 int count = path.elementCount();
88 m_vertices.reset();
89 if (count < 2)
90 return;
91
92 float realWidth = qpen_widthf(p: pen);
93 if (realWidth == 0)
94 realWidth = 1;
95
96 m_width = realWidth / 2;
97
98 bool cosmetic = qt_pen_is_cosmetic(pen, hints);
99 if (cosmetic) {
100 m_width = m_width * m_inv_scale;
101 }
102
103 m_join_style = qpen_joinStyle(p: pen);
104 m_cap_style = qpen_capStyle(p: pen);
105 m_miter_limit = pen.miterLimit() * qpen_widthf(p: pen);
106
107 // The curvyness is based on the notion that I originally wanted
108 // roughly one line segment pr 4 pixels. This may seem little, but
109 // because we sample at constantly incrementing B(t) E [0<t<1], we
110 // will get longer segments where the curvature is small and smaller
111 // segments when the curvature is high.
112 //
113 // To get a rough idea of the length of each curve, I pretend that
114 // the curve is a 90 degree arc, whose radius is
115 // qMax(curveBounds.width, curveBounds.height). Based on this
116 // logic we can estimate the length of the outline edges based on
117 // the radius + a pen width and adjusting for scale factors
118 // depending on if the pen is cosmetic or not.
119 //
120 // The curvyness value of PI/14 was based on,
121 // arcLength = 2*PI*r/4 = PI*r/2 and splitting length into somewhere
122 // between 3 and 8 where 5 seemed to be give pretty good results
123 // hence: Q_PI/14. Lower divisors will give more detail at the
124 // direct cost of performance.
125
126 // simplfy pens that are thin in device size (2px wide or less)
127 if (realWidth < 2.5 && (cosmetic || m_inv_scale == 1)) {
128 if (m_cap_style == Qt::RoundCap)
129 m_cap_style = Qt::SquareCap;
130 if (m_join_style == Qt::RoundJoin)
131 m_join_style = Qt::MiterJoin;
132 m_curvyness_add = 0.5;
133 m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
134 m_roundness = 1;
135 } else if (cosmetic) {
136 m_curvyness_add = realWidth / 2;
137 m_curvyness_mul = float(CURVE_FLATNESS);
138 m_roundness = qMax<int>(a: 4, b: realWidth * CURVE_FLATNESS);
139 } else {
140 m_curvyness_add = m_width;
141 m_curvyness_mul = CURVE_FLATNESS / m_inv_scale;
142 m_roundness = qMax<int>(a: 4, b: realWidth * m_curvyness_mul);
143 }
144
145 // Over this level of segmentation, there doesn't seem to be any
146 // benefit, even for huge penWidth
147 if (m_roundness > 24)
148 m_roundness = 24;
149
150 m_sin_theta = qFastSin(x: Q_PI / m_roundness);
151 m_cos_theta = qFastCos(x: Q_PI / m_roundness);
152
153 const qreal *endPts = pts + (count<<1);
154 const qreal *startPts = nullptr;
155
156 Qt::PenCapStyle cap = m_cap_style;
157
158 if (!types) {
159 skipDuplicatePoints(pts: &pts, endPts);
160 if ((pts + 2) == endPts)
161 return;
162
163 startPts = pts;
164
165 bool endsAtStart = float(startPts[0]) == float(endPts[-2])
166 && float(startPts[1]) == float(endPts[-1]);
167
168 if (endsAtStart || path.hasImplicitClose())
169 m_cap_style = Qt::FlatCap;
170 moveTo(pts);
171 m_cap_style = cap;
172 pts += 2;
173 skipDuplicatePoints(pts: &pts, endPts);
174 lineTo(pts);
175 pts += 2;
176 skipDuplicatePoints(pts: &pts, endPts);
177 while (pts < endPts) {
178 join(pts);
179 lineTo(pts);
180 pts += 2;
181 skipDuplicatePoints(pts: &pts, endPts);
182 }
183 endCapOrJoinClosed(start: startPts, cur: pts-2, implicitClose: path.hasImplicitClose(), endsAtStart);
184
185 } else {
186 bool endsAtStart = false;
187 QPainterPath::ElementType previousType = QPainterPath::MoveToElement;
188 const qreal *previousPts = pts;
189 while (pts < endPts) {
190 switch (*types) {
191 case QPainterPath::MoveToElement: {
192 int end = (endPts - pts) / 2;
193 int nextMoveElement = 1;
194 bool hasValidLineSegments = false;
195 while (nextMoveElement < end && types[nextMoveElement] != QPainterPath::MoveToElement) {
196 if (!hasValidLineSegments) {
197 hasValidLineSegments =
198 float(pts[0]) != float(pts[nextMoveElement * 2]) ||
199 float(pts[1]) != float(pts[nextMoveElement * 2 + 1]);
200 }
201 ++nextMoveElement;
202 }
203
204 /**
205 * 'LineToElement' may be skipped if it doesn't move the center point
206 * of the line. We should make sure that we don't end up with a lost
207 * 'MoveToElement' in the vertex buffer, not connected to anything. Since
208 * the buffer uses degenerate triangles trick to split the primitives,
209 * this spurious MoveToElement will create artifacts when rendering.
210 */
211 if (!hasValidLineSegments) {
212 pts += 2 * nextMoveElement;
213 types += nextMoveElement;
214 continue;
215 }
216
217 if (previousType != QPainterPath::MoveToElement)
218 endCapOrJoinClosed(start: startPts, cur: previousPts, implicitClose: path.hasImplicitClose(), endsAtStart);
219
220 startPts = pts;
221 skipDuplicatePoints(pts: &startPts, endPts); // Skip duplicates to find correct normal.
222 if (startPts + 2 >= endPts)
223 return; // Nothing to see here...
224
225 endsAtStart = float(startPts[0]) == float(pts[nextMoveElement * 2 - 2])
226 && float(startPts[1]) == float(pts[nextMoveElement * 2 - 1]);
227 if (endsAtStart || path.hasImplicitClose())
228 m_cap_style = Qt::FlatCap;
229
230 moveTo(pts: startPts);
231 m_cap_style = cap;
232 previousType = QPainterPath::MoveToElement;
233 previousPts = pts;
234 pts+=2;
235 ++types;
236 break; }
237 case QPainterPath::LineToElement:
238 if (float(m_cx) != float(pts[0]) || float(m_cy) != float(pts[1])) {
239 if (previousType != QPainterPath::MoveToElement)
240 join(pts);
241 lineTo(pts);
242 previousType = QPainterPath::LineToElement;
243 previousPts = pts;
244 }
245 pts+=2;
246 ++types;
247 break;
248 case QPainterPath::CurveToElement:
249 if (float(m_cx) != float(pts[0]) || float(m_cy) != float(pts[1])
250 || float(pts[0]) != float(pts[2]) || float(pts[1]) != float(pts[3])
251 || float(pts[2]) != float(pts[4]) || float(pts[3]) != float(pts[5]))
252 {
253 if (float(m_cx) != float(pts[0]) || float(m_cy) != float(pts[1])) {
254 if (previousType != QPainterPath::MoveToElement)
255 join(pts);
256 }
257 cubicTo(pts);
258 previousType = QPainterPath::CurveToElement;
259 previousPts = pts + 4;
260 }
261 pts+=6;
262 types+=3;
263 break;
264 default:
265 Q_ASSERT(false);
266 break;
267 }
268 }
269
270 if (previousType != QPainterPath::MoveToElement)
271 endCapOrJoinClosed(start: startPts, cur: previousPts, implicitClose: path.hasImplicitClose(), endsAtStart);
272 }
273}
274
275void QTriangulatingStroker::moveTo(const qreal *pts)
276{
277 m_cx = pts[0];
278 m_cy = pts[1];
279
280 float x2 = pts[2];
281 float y2 = pts[3];
282 normalVector(x1: m_cx, y1: m_cy, x2, y2, nx: &m_nvx, ny: &m_nvy);
283
284
285 // To achieve jumps we insert zero-area tringles. This is done by
286 // adding two identical points in both the end of previous strip
287 // and beginning of next strip
288 bool invisibleJump = m_vertices.size();
289
290 switch (m_cap_style) {
291 case Qt::FlatCap:
292 if (invisibleJump) {
293 m_vertices.add(t: m_cx + m_nvx);
294 m_vertices.add(t: m_cy + m_nvy);
295 }
296 break;
297 case Qt::SquareCap: {
298 float sx = m_cx - m_nvy;
299 float sy = m_cy + m_nvx;
300 if (invisibleJump) {
301 m_vertices.add(t: sx + m_nvx);
302 m_vertices.add(t: sy + m_nvy);
303 }
304 emitLineSegment(x: sx, y: sy, vx: m_nvx, vy: m_nvy);
305 break; }
306 case Qt::RoundCap: {
307 QVarLengthArray<float> points;
308 arcPoints(cx: m_cx, cy: m_cy, fromX: m_cx + m_nvx, fromY: m_cy + m_nvy, toX: m_cx - m_nvx, toY: m_cy - m_nvy, points);
309 m_vertices.resize(size: m_vertices.size() + points.size() + 2 * int(invisibleJump));
310 int count = m_vertices.size();
311 int front = 0;
312 int end = points.size() / 2;
313 while (front != end) {
314 m_vertices.at(i: --count) = points[2 * end - 1];
315 m_vertices.at(i: --count) = points[2 * end - 2];
316 --end;
317 if (front == end)
318 break;
319 m_vertices.at(i: --count) = points[2 * front + 1];
320 m_vertices.at(i: --count) = points[2 * front + 0];
321 ++front;
322 }
323
324 if (invisibleJump) {
325 m_vertices.at(i: count - 1) = m_vertices.at(i: count + 1);
326 m_vertices.at(i: count - 2) = m_vertices.at(i: count + 0);
327 }
328 break; }
329 default: break; // ssssh gcc...
330 }
331 emitLineSegment(x: m_cx, y: m_cy, vx: m_nvx, vy: m_nvy);
332}
333
334void QTriangulatingStroker::cubicTo(const qreal *pts)
335{
336 const QPointF *p = (const QPointF *) pts;
337 QBezier bezier = QBezier::fromPoints(p1: *(p - 1), p2: p[0], p3: p[1], p4: p[2]);
338
339 QRectF bounds = bezier.bounds();
340 float rad = qMax(a: bounds.width(), b: bounds.height());
341 int threshold = qMin<float>(a: 64, b: (rad + m_curvyness_add) * m_curvyness_mul);
342 if (threshold < 4)
343 threshold = 4;
344 qreal threshold_minus_1 = threshold - 1;
345 float vx = 0, vy = 0;
346
347 float cx = m_cx, cy = m_cy;
348 float x, y;
349
350 for (int i=1; i<threshold; ++i) {
351 qreal t = qreal(i) / threshold_minus_1;
352 QPointF p = bezier.pointAt(t);
353 x = p.x();
354 y = p.y();
355
356 normalVector(x1: cx, y1: cy, x2: x, y2: y, nx: &vx, ny: &vy);
357
358 emitLineSegment(x, y, vx, vy);
359
360 cx = x;
361 cy = y;
362 }
363
364 m_cx = cx;
365 m_cy = cy;
366
367 m_nvx = vx;
368 m_nvy = vy;
369}
370
371void QTriangulatingStroker::join(const qreal *pts)
372{
373 // Creates a join to the next segment (m_cx, m_cy) -> (pts[0], pts[1])
374 normalVector(x1: m_cx, y1: m_cy, x2: pts[0], y2: pts[1], nx: &m_nvx, ny: &m_nvy);
375
376 switch (m_join_style) {
377 case Qt::BevelJoin:
378 break;
379 case Qt::SvgMiterJoin:
380 case Qt::MiterJoin: {
381 // Find out on which side the join should be.
382 int count = m_vertices.size();
383 float prevNvx = m_vertices.at(i: count - 2) - m_cx;
384 float prevNvy = m_vertices.at(i: count - 1) - m_cy;
385 float xprod = prevNvx * m_nvy - prevNvy * m_nvx;
386 float px, py, qx, qy;
387
388 // If the segments are parallel, use bevel join.
389 if (qFuzzyIsNull(f: xprod))
390 break;
391
392 // Find the corners of the previous and next segment to join.
393 if (xprod < 0) {
394 px = m_vertices.at(i: count - 2);
395 py = m_vertices.at(i: count - 1);
396 qx = m_cx - m_nvx;
397 qy = m_cy - m_nvy;
398 } else {
399 px = m_vertices.at(i: count - 4);
400 py = m_vertices.at(i: count - 3);
401 qx = m_cx + m_nvx;
402 qy = m_cy + m_nvy;
403 }
404
405 // Find intersection point.
406 float pu = px * prevNvx + py * prevNvy;
407 float qv = qx * m_nvx + qy * m_nvy;
408 float ix = (m_nvy * pu - prevNvy * qv) / xprod;
409 float iy = (prevNvx * qv - m_nvx * pu) / xprod;
410
411 // Check that the distance to the intersection point is less than the miter limit.
412 if ((ix - px) * (ix - px) + (iy - py) * (iy - py) <= m_miter_limit * m_miter_limit) {
413 m_vertices.add(t: ix);
414 m_vertices.add(t: iy);
415 m_vertices.add(t: ix);
416 m_vertices.add(t: iy);
417 }
418 // else
419 // Do a plain bevel join if the miter limit is exceeded or if
420 // the lines are parallel. This is not what the raster
421 // engine's stroker does, but it is both faster and similar to
422 // what some other graphics API's do.
423
424 break; }
425 case Qt::RoundJoin: {
426 QVarLengthArray<float> points;
427 int count = m_vertices.size();
428 float prevNvx = m_vertices.at(i: count - 2) - m_cx;
429 float prevNvy = m_vertices.at(i: count - 1) - m_cy;
430 if (m_nvx * prevNvy - m_nvy * prevNvx < 0) {
431 arcPoints(cx: 0, cy: 0, fromX: m_nvx, fromY: m_nvy, toX: -prevNvx, toY: -prevNvy, points);
432 for (int i = points.size() / 2; i > 0; --i)
433 emitLineSegment(x: m_cx, y: m_cy, vx: points[2 * i - 2], vy: points[2 * i - 1]);
434 } else {
435 arcPoints(cx: 0, cy: 0, fromX: -prevNvx, fromY: -prevNvy, toX: m_nvx, toY: m_nvy, points);
436 for (int i = 0; i < points.size() / 2; ++i)
437 emitLineSegment(x: m_cx, y: m_cy, vx: points[2 * i + 0], vy: points[2 * i + 1]);
438 }
439 break; }
440 default: break; // gcc warn--
441 }
442
443 emitLineSegment(x: m_cx, y: m_cy, vx: m_nvx, vy: m_nvy);
444}
445
446void QTriangulatingStroker::endCap(const qreal *)
447{
448 switch (m_cap_style) {
449 case Qt::FlatCap:
450 break;
451 case Qt::SquareCap:
452 emitLineSegment(x: m_cx + m_nvy, y: m_cy - m_nvx, vx: m_nvx, vy: m_nvy);
453 break;
454 case Qt::RoundCap: {
455 QVarLengthArray<float> points;
456 int count = m_vertices.size();
457 arcPoints(cx: m_cx, cy: m_cy, fromX: m_vertices.at(i: count - 2), fromY: m_vertices.at(i: count - 1), toX: m_vertices.at(i: count - 4), toY: m_vertices.at(i: count - 3), points);
458 int front = 0;
459 int end = points.size() / 2;
460 while (front != end) {
461 m_vertices.add(t: points[2 * end - 2]);
462 m_vertices.add(t: points[2 * end - 1]);
463 --end;
464 if (front == end)
465 break;
466 m_vertices.add(t: points[2 * front + 0]);
467 m_vertices.add(t: points[2 * front + 1]);
468 ++front;
469 }
470 break; }
471 default: break; // to shut gcc up...
472 }
473}
474
475void QTriangulatingStroker::arcPoints(float cx, float cy, float fromX, float fromY, float toX, float toY, QVarLengthArray<float> &points)
476{
477 float dx1 = fromX - cx;
478 float dy1 = fromY - cy;
479 float dx2 = toX - cx;
480 float dy2 = toY - cy;
481
482 // while more than 180 degrees left:
483 while (dx1 * dy2 - dx2 * dy1 < 0) {
484 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
485 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
486 dx1 = tmpx;
487 dy1 = tmpy;
488 points.append(t: cx + dx1);
489 points.append(t: cy + dy1);
490 }
491
492 // while more than 90 degrees left:
493 while (dx1 * dx2 + dy1 * dy2 < 0) {
494 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
495 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
496 dx1 = tmpx;
497 dy1 = tmpy;
498 points.append(t: cx + dx1);
499 points.append(t: cy + dy1);
500 }
501
502 // while more than 0 degrees left:
503 while (dx1 * dy2 - dx2 * dy1 > 0) {
504 float tmpx = dx1 * m_cos_theta - dy1 * m_sin_theta;
505 float tmpy = dx1 * m_sin_theta + dy1 * m_cos_theta;
506 dx1 = tmpx;
507 dy1 = tmpy;
508 points.append(t: cx + dx1);
509 points.append(t: cy + dy1);
510 }
511
512 // remove last point which was rotated beyond [toX, toY].
513 if (!points.isEmpty())
514 points.resize(asize: points.size() - 2);
515}
516
517static void qdashprocessor_moveTo(qreal x, qreal y, void *data)
518{
519 ((QDashedStrokeProcessor *) data)->addElement(type: QPainterPath::MoveToElement, x, y);
520}
521
522static void qdashprocessor_lineTo(qreal x, qreal y, void *data)
523{
524 ((QDashedStrokeProcessor *) data)->addElement(type: QPainterPath::LineToElement, x, y);
525}
526
527static void qdashprocessor_cubicTo(qreal, qreal, qreal, qreal, qreal, qreal, void *)
528{
529 Q_ASSERT(0); // The dasher should not produce curves...
530}
531
532QDashedStrokeProcessor::QDashedStrokeProcessor()
533 : m_points(0), m_types(0),
534 m_dash_stroker(nullptr), m_inv_scale(1)
535{
536 m_dash_stroker.setMoveToHook(qdashprocessor_moveTo);
537 m_dash_stroker.setLineToHook(qdashprocessor_lineTo);
538 m_dash_stroker.setCubicToHook(qdashprocessor_cubicTo);
539}
540
541void QDashedStrokeProcessor::process(const QVectorPath &path, const QPen &pen, const QRectF &clip, QPainter::RenderHints hints)
542{
543
544 const qreal *pts = path.points();
545 const QPainterPath::ElementType *types = path.elements();
546 int count = path.elementCount();
547
548 bool cosmetic = qt_pen_is_cosmetic(pen, hints);
549 bool implicitClose = path.hasImplicitClose();
550
551 m_points.reset();
552 m_types.reset();
553 m_points.reserve(size: path.elementCount());
554 m_types.reserve(size: path.elementCount());
555
556 qreal width = qpen_widthf(p: pen);
557 if (width == 0)
558 width = 1;
559
560 m_dash_stroker.setDashPattern(pen.dashPattern());
561 m_dash_stroker.setStrokeWidth(cosmetic ? width * m_inv_scale : width);
562 m_dash_stroker.setDashOffset(pen.dashOffset());
563 m_dash_stroker.setMiterLimit(pen.miterLimit());
564 m_dash_stroker.setClipRect(clip);
565
566 float curvynessAdd, curvynessMul;
567
568 // simplify pens that are thin in device size (2px wide or less)
569 if (width < 2.5 && (cosmetic || m_inv_scale == 1)) {
570 curvynessAdd = 0.5;
571 curvynessMul = CURVE_FLATNESS / m_inv_scale;
572 } else if (cosmetic) {
573 curvynessAdd= width / 2;
574 curvynessMul= float(CURVE_FLATNESS);
575 } else {
576 curvynessAdd = width * m_inv_scale;
577 curvynessMul = CURVE_FLATNESS / m_inv_scale;
578 }
579
580 if (count < 2)
581 return;
582
583 bool needsClose = false;
584 if (implicitClose) {
585 if (pts[0] != pts[count * 2 - 2] || pts[1] != pts[count * 2 - 1])
586 needsClose = true;
587 }
588
589 const qreal *firstPts = pts;
590 const qreal *endPts = pts + (count<<1);
591 m_dash_stroker.begin(data: this);
592
593 if (!types) {
594 m_dash_stroker.moveTo(x: pts[0], y: pts[1]);
595 pts += 2;
596 while (pts < endPts) {
597 m_dash_stroker.lineTo(x: pts[0], y: pts[1]);
598 pts += 2;
599 }
600 } else {
601 while (pts < endPts) {
602 switch (*types) {
603 case QPainterPath::MoveToElement:
604 m_dash_stroker.moveTo(x: pts[0], y: pts[1]);
605 pts += 2;
606 ++types;
607 break;
608 case QPainterPath::LineToElement:
609 m_dash_stroker.lineTo(x: pts[0], y: pts[1]);
610 pts += 2;
611 ++types;
612 break;
613 case QPainterPath::CurveToElement: {
614 QBezier b = QBezier::fromPoints(p1: *(((const QPointF *) pts) - 1),
615 p2: *(((const QPointF *) pts)),
616 p3: *(((const QPointF *) pts) + 1),
617 p4: *(((const QPointF *) pts) + 2));
618 QRectF bounds = b.bounds();
619 float rad = qMax(a: bounds.width(), b: bounds.height());
620 int threshold = qMin<float>(a: 64, b: (rad + curvynessAdd) * curvynessMul);
621 if (threshold < 4)
622 threshold = 4;
623
624 qreal threshold_minus_1 = threshold - 1;
625 for (int i=0; i<threshold; ++i) {
626 QPointF pt = b.pointAt(t: i / threshold_minus_1);
627 m_dash_stroker.lineTo(x: pt.x(), y: pt.y());
628 }
629 pts += 6;
630 types += 3;
631 break; }
632 default: break;
633 }
634 }
635 }
636 if (needsClose)
637 m_dash_stroker.lineTo(x: firstPts[0], y: firstPts[1]);
638
639 m_dash_stroker.end();
640}
641
642QT_END_NAMESPACE
643

source code of qtbase/src/gui/painting/qtriangulatingstroker.cpp