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