1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qoutlinemapper_p.h"
5
6#include "qbezier_p.h"
7#include "qmath.h"
8#include "qpainterpath_p.h"
9#include "qscopedvaluerollback.h"
10
11#include <stdlib.h>
12
13QT_BEGIN_NAMESPACE
14
15#define qreal_to_fixed_26_6(f) (qRound(f * 64))
16
17
18
19
20static const QRectF boundingRect(const QPointF *points, int pointCount)
21{
22 const QPointF *e = points;
23 const QPointF *last = points + pointCount;
24 qreal minx, maxx, miny, maxy;
25 minx = maxx = e->x();
26 miny = maxy = e->y();
27 while (++e < last) {
28 if (e->x() < minx)
29 minx = e->x();
30 else if (e->x() > maxx)
31 maxx = e->x();
32 if (e->y() < miny)
33 miny = e->y();
34 else if (e->y() > maxy)
35 maxy = e->y();
36 }
37 return QRectF(QPointF(minx, miny), QPointF(maxx, maxy));
38}
39
40void QOutlineMapper::setClipRect(QRect clipRect)
41{
42 auto limitCoords = [](QRect r) {
43 const QRect limitRect(QPoint(-QT_RASTER_COORD_LIMIT, -QT_RASTER_COORD_LIMIT),
44 QPoint(QT_RASTER_COORD_LIMIT, QT_RASTER_COORD_LIMIT));
45 r &= limitRect;
46 r.setWidth(qMin(a: r.width(), b: QT_RASTER_COORD_LIMIT));
47 r.setHeight(qMin(a: r.height(), b: QT_RASTER_COORD_LIMIT));
48 return r;
49 };
50
51 if (clipRect != m_clip_rect) {
52 m_clip_rect = limitCoords(clipRect);
53 const int mw = 64; // margin width. No need to trigger clipping for slight overshooting
54 m_clip_trigger_rect = QRectF(limitCoords(m_clip_rect.adjusted(xp1: -mw, yp1: -mw, xp2: mw, yp2: mw)));
55 }
56}
57
58void QOutlineMapper::curveTo(const QPointF &cp1, const QPointF &cp2, const QPointF &ep) {
59#ifdef QT_DEBUG_CONVERT
60 printf("QOutlineMapper::curveTo() (%f, %f)\n", ep.x(), ep.y());
61#endif
62
63 if (!m_elements.size())
64 return;
65 QBezier bezier = QBezier::fromPoints(p1: m_elements.last(), p2: cp1, p3: cp2, p4: ep);
66
67 bool outsideClip = false;
68 // Test one point first before doing a full intersection test.
69 if (!QRectF(m_clip_rect).contains(p: m_transform.map(p: ep))) {
70 QRectF potentialCurveArea = m_transform.mapRect(bezier.bounds());
71 outsideClip = !potentialCurveArea.intersects(r: m_clip_rect);
72 }
73 if (outsideClip) {
74 // The curve is entirely outside the clip rect, so just
75 // approximate it with a line that closes the path.
76 lineTo(pt: ep);
77 } else {
78 bezier.addToPolygon(polygon&: m_elements, bezier_flattening_threshold: m_curve_threshold);
79 m_element_types.reserve(size: m_elements.size());
80 for (int i = m_elements.size() - m_element_types.size(); i; --i)
81 m_element_types << QPainterPath::LineToElement;
82 }
83 Q_ASSERT(m_elements.size() == m_element_types.size());
84}
85
86
87QT_FT_Outline *QOutlineMapper::convertPath(const QPainterPath &path)
88{
89 Q_ASSERT(!path.isEmpty());
90 int elmCount = path.elementCount();
91#ifdef QT_DEBUG_CONVERT
92 printf("QOutlineMapper::convertPath(), size=%d\n", elmCount);
93#endif
94 beginOutline(fillRule: path.fillRule());
95
96 for (int index=0; index<elmCount; ++index) {
97 const QPainterPath::Element &elm = path.elementAt(i: index);
98
99 switch (elm.type) {
100
101 case QPainterPath::MoveToElement:
102 if (index == elmCount - 1)
103 continue;
104 moveTo(pt: elm);
105 break;
106
107 case QPainterPath::LineToElement:
108 lineTo(pt: elm);
109 break;
110
111 case QPainterPath::CurveToElement:
112 curveTo(cp1: elm, cp2: path.elementAt(i: index + 1), ep: path.elementAt(i: index + 2));
113 index += 2;
114 break;
115
116 default:
117 break; // This will never hit..
118 }
119 }
120
121 endOutline();
122 return outline();
123}
124
125QT_FT_Outline *QOutlineMapper::convertPath(const QVectorPath &path)
126{
127 int count = path.elementCount();
128
129#ifdef QT_DEBUG_CONVERT
130 printf("QOutlineMapper::convertPath(VP), size=%d\n", count);
131#endif
132 beginOutline(fillRule: path.hasWindingFill() ? Qt::WindingFill : Qt::OddEvenFill);
133
134 if (path.elements()) {
135 // TODO: if we do closing of subpaths in convertElements instead we
136 // could avoid this loop
137 const QPainterPath::ElementType *elements = path.elements();
138 const QPointF *points = reinterpret_cast<const QPointF *>(path.points());
139
140 for (int index = 0; index < count; ++index) {
141 switch (elements[index]) {
142 case QPainterPath::MoveToElement:
143 if (index == count - 1)
144 continue;
145 moveTo(pt: points[index]);
146 break;
147
148 case QPainterPath::LineToElement:
149 lineTo(pt: points[index]);
150 break;
151
152 case QPainterPath::CurveToElement:
153 curveTo(cp1: points[index], cp2: points[index+1], ep: points[index+2]);
154 index += 2;
155 break;
156
157 default:
158 break; // This will never hit..
159 }
160 }
161
162 } else {
163 // ### We can kill this copying and just use the buffer straight...
164
165 m_elements.resize(size: count);
166 if (count)
167 memcpy(dest: static_cast<void *>(m_elements.data()), src: static_cast<const void *>(path.points()), n: count* sizeof(QPointF));
168
169 m_element_types.resize(size: 0);
170 }
171
172 endOutline();
173 return outline();
174}
175
176
177void QOutlineMapper::endOutline()
178{
179 closeSubpath();
180
181 if (m_elements.isEmpty()) {
182 memset(s: &m_outline, c: 0, n: sizeof(m_outline));
183 return;
184 }
185
186 QPointF *elements = m_elements.data();
187
188 // Transform the outline
189 if (m_transform.isIdentity()) {
190 // Nothing to do
191 } else if (m_transform.type() < QTransform::TxProject) {
192 for (int i = 0; i < m_elements.size(); ++i)
193 elements[i] = m_transform.map(p: elements[i]);
194 } else {
195 const QVectorPath vp((qreal *)elements, m_elements.size(),
196 m_element_types.size() ? m_element_types.data() : nullptr);
197 QPainterPath path = vp.convertToPainterPath();
198 path = m_transform.map(p: path);
199 if (!(m_outline.flags & QT_FT_OUTLINE_EVEN_ODD_FILL))
200 path.setFillRule(Qt::WindingFill);
201 if (path.isEmpty()) {
202 m_valid = false;
203 } else {
204 QTransform oldTransform = m_transform;
205 m_transform.reset();
206 convertPath(path);
207 m_transform = oldTransform;
208 }
209 return;
210 }
211
212 controlPointRect = boundingRect(points: elements, pointCount: m_elements.size());
213
214#ifdef QT_DEBUG_CONVERT
215 printf(" - control point rect (%.2f, %.2f) %.2f x %.2f, clip=(%d,%d, %dx%d)\n",
216 controlPointRect.x(), controlPointRect.y(),
217 controlPointRect.width(), controlPointRect.height(),
218 m_clip_rect.x(), m_clip_rect.y(), m_clip_rect.width(), m_clip_rect.height());
219#endif
220
221 // Avoid rasterizing outside cliprect: faster, and ensures coords < QT_RASTER_COORD_LIMIT
222 if (!m_in_clip_elements && !m_clip_trigger_rect.contains(r: controlPointRect)) {
223 clipElements(points: elements, types: elementTypes(), count: m_elements.size());
224 } else {
225 convertElements(points: elements, types: elementTypes(), count: m_elements.size());
226 }
227}
228
229void QOutlineMapper::convertElements(const QPointF *elements,
230 const QPainterPath::ElementType *types,
231 int element_count)
232{
233
234 if (types) {
235 // Translate into FT coords
236 const QPointF *e = elements;
237 for (int i=0; i<element_count; ++i) {
238 switch (*types) {
239 case QPainterPath::MoveToElement:
240 {
241 QT_FT_Vector pt_fixed = { qreal_to_fixed_26_6(e->x()),
242 qreal_to_fixed_26_6(e->y()) };
243 if (i != 0)
244 m_contours << m_points.size() - 1;
245 m_points << pt_fixed;
246 m_tags << QT_FT_CURVE_TAG_ON;
247 }
248 break;
249
250 case QPainterPath::LineToElement:
251 {
252 QT_FT_Vector pt_fixed = { qreal_to_fixed_26_6(e->x()),
253 qreal_to_fixed_26_6(e->y()) };
254 m_points << pt_fixed;
255 m_tags << QT_FT_CURVE_TAG_ON;
256 }
257 break;
258
259 case QPainterPath::CurveToElement:
260 {
261 QT_FT_Vector cp1_fixed = { qreal_to_fixed_26_6(e->x()),
262 qreal_to_fixed_26_6(e->y()) };
263 ++e;
264 QT_FT_Vector cp2_fixed = { qreal_to_fixed_26_6((e)->x()),
265 qreal_to_fixed_26_6((e)->y()) };
266 ++e;
267 QT_FT_Vector ep_fixed = { qreal_to_fixed_26_6((e)->x()),
268 qreal_to_fixed_26_6((e)->y()) };
269
270 m_points << cp1_fixed << cp2_fixed << ep_fixed;
271 m_tags << QT_FT_CURVE_TAG_CUBIC
272 << QT_FT_CURVE_TAG_CUBIC
273 << QT_FT_CURVE_TAG_ON;
274
275 types += 2;
276 i += 2;
277 }
278 break;
279 default:
280 break;
281 }
282 ++types;
283 ++e;
284 }
285 } else {
286 // Plain polygon...
287 const QPointF *last = elements + element_count;
288 const QPointF *e = elements;
289 while (e < last) {
290 QT_FT_Vector pt_fixed = { qreal_to_fixed_26_6(e->x()),
291 qreal_to_fixed_26_6(e->y()) };
292 m_points << pt_fixed;
293 m_tags << QT_FT_CURVE_TAG_ON;
294 ++e;
295 }
296 }
297
298 // close the very last subpath
299 m_contours << m_points.size() - 1;
300
301 m_outline.n_contours = m_contours.size();
302 m_outline.n_points = m_points.size();
303
304 m_outline.points = m_points.data();
305 m_outline.tags = m_tags.data();
306 m_outline.contours = m_contours.data();
307
308#ifdef QT_DEBUG_CONVERT
309 printf("QOutlineMapper::endOutline\n");
310
311 printf(" - contours: %d\n", m_outline.n_contours);
312 for (int i=0; i<m_outline.n_contours; ++i) {
313 printf(" - %d\n", m_outline.contours[i]);
314 }
315
316 printf(" - points: %d\n", m_outline.n_points);
317 for (int i=0; i<m_outline.n_points; ++i) {
318 printf(" - %d -- %.2f, %.2f, (%d, %d)\n", i,
319 (double) (m_outline.points[i].x / 64.0),
320 (double) (m_outline.points[i].y / 64.0),
321 (int) m_outline.points[i].x, (int) m_outline.points[i].y);
322 }
323#endif
324}
325
326void QOutlineMapper::clipElements(const QPointF *elements,
327 const QPainterPath::ElementType *types,
328 int element_count)
329{
330 // We could save a bit of time by actually implementing them fully
331 // instead of going through convenience functionality, but since
332 // this part of code hardly every used, it shouldn't matter.
333
334 QScopedValueRollback<bool> in_clip_elements(m_in_clip_elements, true);
335
336 QPainterPath path;
337
338 if (!(m_outline.flags & QT_FT_OUTLINE_EVEN_ODD_FILL))
339 path.setFillRule(Qt::WindingFill);
340
341 if (types) {
342 for (int i=0; i<element_count; ++i) {
343 switch (types[i]) {
344 case QPainterPath::MoveToElement:
345 path.moveTo(p: elements[i]);
346 break;
347
348 case QPainterPath::LineToElement:
349 path.lineTo(p: elements[i]);
350 break;
351
352 case QPainterPath::CurveToElement:
353 path.cubicTo(ctrlPt1: elements[i], ctrlPt2: elements[i+1], endPt: elements[i+2]);
354 i += 2;
355 break;
356 default:
357 break;
358 }
359 }
360 } else {
361 path.moveTo(p: elements[0]);
362 for (int i=1; i<element_count; ++i)
363 path.lineTo(p: elements[i]);
364 }
365
366 QPainterPath clipPath;
367 clipPath.addRect(rect: m_clip_rect);
368 QPainterPath clippedPath = path.intersected(r: clipPath);
369 if (clippedPath.isEmpty()) {
370 m_valid = false;
371 } else {
372 QTransform oldTransform = m_transform;
373 m_transform.reset();
374 convertPath(path: clippedPath);
375 m_transform = oldTransform;
376 }
377}
378
379QT_END_NAMESPACE
380

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