1// Copyright (C) 2024 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 "qquickitemgenerator_p.h"
5#include "utils_p.h"
6#include "qquicknodeinfo_p.h"
7
8#include <private/qsgcurveprocessor_p.h>
9#include <private/qquickshape_p.h>
10#include <private/qquadpath_p.h>
11#include <private/qquickitem_p.h>
12#include <private/qquickimagebase_p_p.h>
13
14#include <QtCore/qloggingcategory.h>
15
16QT_BEGIN_NAMESPACE
17
18Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage)
19
20QQuickItemGenerator::QQuickItemGenerator(const QString fileName, QQuickVectorImageGenerator::GeneratorFlags flags, QQuickItem *parentItem)
21 :QQuickGenerator(fileName, flags)
22{
23 Q_ASSERT(parentItem);
24 m_items.push(t: parentItem);
25 m_parentItem = parentItem;
26}
27
28QQuickItemGenerator::~QQuickItemGenerator()
29{
30}
31
32void QQuickItemGenerator::generateNodeBase(const NodeInfo &info)
33{
34 if (!info.isDefaultTransform) {
35 auto sx = info.transform.m11();
36 auto sy = info.transform.m22();
37 auto x = info.transform.m31();
38 auto y = info.transform.m32();
39
40 auto xformProp = currentItem()->transform();
41 if (info.transform.type() == QTransform::TxTranslate) {
42 auto *translate = new QQuickTranslate;
43 translate->setX(x);
44 translate->setY(y);
45 xformProp.append(&xformProp, translate);
46 } else if (info.transform.type() == QTransform::TxScale && !x && !y) {
47 auto scale = new QQuickScale;
48 scale->setParent(currentItem());
49 scale->setXScale(sx);
50 scale->setYScale(sy);
51 xformProp.append(&xformProp, scale);
52 } else {
53 const QMatrix4x4 m(info.transform);
54 auto xform = new QQuickMatrix4x4;
55 xform->setMatrix(m);
56 xformProp.append(&xformProp, xform);
57 }
58 }
59 if (!info.isDefaultOpacity) {
60 currentItem()->setOpacity(info.opacity);
61 }
62}
63
64bool QQuickItemGenerator::generateDefsNode(const NodeInfo &info)
65{
66 Q_UNUSED(info)
67
68 return false;
69}
70
71void QQuickItemGenerator::generateImageNode(const ImageNodeInfo &info)
72{
73 if (!isNodeVisible(info))
74 return;
75
76 auto *imageItem = new QQuickImage;
77 auto *imagePriv = static_cast<QQuickImageBasePrivate*>(QQuickItemPrivate::get(item: imageItem));
78 imagePriv->currentPix->setImage(info.image);
79
80 imageItem->setX(info.rect.x());
81 imageItem->setY(info.rect.y());
82 imageItem->setWidth(info.rect.width());
83 imageItem->setHeight(info.rect.height());
84
85 addCurrentItem(item: imageItem, info);
86 generateNodeBase(info);
87
88 m_items.pop();
89}
90
91void QQuickItemGenerator::generatePath(const PathNodeInfo &info, const QRectF &overrideBoundingRect)
92{
93 if (!isNodeVisible(info))
94 return;
95
96 if (m_inShapeItem) {
97 if (!info.isDefaultTransform)
98 qCWarning(lcQuickVectorImage) << "Skipped transform for node" << info.nodeId << "type" << info.typeName << "(this is not supposed to happen)";
99 optimizePaths(info, overrideBoundingRect);
100 } else {
101 auto *shapeItem = new QQuickShape;
102 if (m_flags.testFlag(flag: QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
103 shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer);
104 shapeItem->setContainsMode(QQuickShape::ContainsMode::FillContains); // TODO: configurable?
105 addCurrentItem(item: shapeItem, info);
106 m_parentShapeItem = shapeItem;
107 m_inShapeItem = true;
108
109 generateNodeBase(info);
110
111 optimizePaths(info, overrideBoundingRect);
112 //qCDebug(lcQuickVectorGraphics) << *node->qpath();
113 m_items.pop();
114 m_inShapeItem = false;
115 m_parentShapeItem = nullptr;
116 }
117}
118
119void QQuickItemGenerator::outputShapePath(const PathNodeInfo &info, const QPainterPath *painterPath, const QQuadPath *quadPath, QQuickVectorImageGenerator::PathSelector pathSelector, const QRectF &boundingRect)
120{
121 Q_UNUSED(pathSelector)
122 Q_ASSERT(painterPath || quadPath);
123
124 const bool noPen = info.strokeStyle.color == QColorConstants::Transparent;
125 if (pathSelector == QQuickVectorImageGenerator::StrokePath && noPen)
126 return;
127
128 const bool noFill = info.grad.type() == QGradient::NoGradient && info.fillColor == QColorConstants::Transparent;
129
130 if (pathSelector == QQuickVectorImageGenerator::FillPath && noFill)
131 return;
132
133 QQuickShapePath::FillRule fillRule = QQuickShapePath::FillRule(painterPath ? painterPath->fillRule() : quadPath->fillRule());
134
135 QQuickShapePath *shapePath = new QQuickShapePath;
136 Q_ASSERT(shapePath);
137
138 if (!info.nodeId.isEmpty())
139 shapePath->setObjectName(QStringLiteral("svg_path:") + info.nodeId);
140
141 if (noPen || !(pathSelector & QQuickVectorImageGenerator::StrokePath)) {
142 shapePath->setStrokeColor(Qt::transparent);
143 } else {
144 shapePath->setStrokeColor(info.strokeStyle.color);
145 shapePath->setStrokeWidth(info.strokeStyle.width);
146 shapePath->setCapStyle(QQuickShapePath::CapStyle(info.strokeStyle.lineCapStyle));
147 shapePath->setJoinStyle(QQuickShapePath::JoinStyle(info.strokeStyle.lineJoinStyle));
148 shapePath->setMiterLimit(info.strokeStyle.miterLimit);
149 if (info.strokeStyle.dashArray.length() != 0) {
150 shapePath->setStrokeStyle(QQuickShapePath::DashLine);
151 shapePath->setDashPattern(info.strokeStyle.dashArray.toVector());
152 shapePath->setDashOffset(info.strokeStyle.dashOffset);
153 }
154 }
155
156 QTransform fillTransform = info.fillTransform;
157 if (!(pathSelector & QQuickVectorImageGenerator::FillPath)) {
158 shapePath->setFillColor(Qt::transparent);
159 } else if (info.grad.type() != QGradient::NoGradient) {
160 generateGradient(grad: &info.grad, shapePath);
161 if (info.grad.coordinateMode() == QGradient::ObjectMode) {
162 QTransform objectToUserSpace;
163 objectToUserSpace.translate(dx: boundingRect.x(), dy: boundingRect.y());
164 objectToUserSpace.scale(sx: boundingRect.width(), sy: boundingRect.height());
165 fillTransform *= objectToUserSpace;
166 }
167 } else {
168 shapePath->setFillColor(info.fillColor);
169 }
170
171 shapePath->setFillRule(fillRule);
172 if (!fillTransform.isIdentity())
173 shapePath->setFillTransform(fillTransform);
174
175 QString svgPathString = painterPath ? QQuickVectorImageGenerator::Utils::toSvgString(path: *painterPath) : QQuickVectorImageGenerator::Utils::toSvgString(path: *quadPath);
176
177 auto *pathSvg = new QQuickPathSvg;
178 pathSvg->setPath(svgPathString);
179 pathSvg->setParent(shapePath);
180
181 auto pathElementProp = shapePath->pathElements();
182 pathElementProp.append(&pathElementProp, pathSvg);
183
184 shapePath->setParent(currentItem());
185 auto shapeDataProp = m_parentShapeItem->data();
186 shapeDataProp.append(&shapeDataProp, shapePath);
187}
188
189void QQuickItemGenerator::generateGradient(const QGradient *grad, QQuickShapePath *shapePath)
190{
191 if (!shapePath)
192 return;
193
194 auto setStops = [=](QQuickShapeGradient *quickGrad, const QGradientStops &stops) {
195 auto stopsProp = quickGrad->stops();
196 for (auto &stop : stops) {
197 auto *stopObj = new QQuickGradientStop(quickGrad);
198 stopObj->setPosition(stop.first);
199 stopObj->setColor(stop.second);
200 stopsProp.append(&stopsProp, stopObj);
201 }
202 };
203
204 if (grad->type() == QGradient::LinearGradient) {
205 auto *linGrad = static_cast<const QLinearGradient *>(grad);
206
207 auto *quickGrad = new QQuickShapeLinearGradient(shapePath);
208 quickGrad->setX1(linGrad->start().x());
209 quickGrad->setY1(linGrad->start().y());
210 quickGrad->setX2(linGrad->finalStop().x());
211 quickGrad->setY2(linGrad->finalStop().y());
212 setStops(quickGrad, linGrad->stops());
213
214 shapePath->setFillGradient(quickGrad);
215 } else if (grad->type() == QGradient::RadialGradient) {
216 auto *radGrad = static_cast<const QRadialGradient*>(grad);
217 auto *quickGrad = new QQuickShapeRadialGradient(shapePath);
218 quickGrad->setCenterX(radGrad->center().x());
219 quickGrad->setCenterY(radGrad->center().y());
220 quickGrad->setCenterRadius(radGrad->radius());
221 quickGrad->setFocalX(radGrad->focalPoint().x());
222 quickGrad->setFocalY(radGrad->focalPoint().y());
223 setStops(quickGrad, radGrad->stops());
224
225 shapePath->setFillGradient(quickGrad);
226 }
227}
228
229void QQuickItemGenerator::generateNode(const NodeInfo &info)
230{
231 if (!isNodeVisible(info))
232 return;
233
234 qCWarning(lcQuickVectorImage) << "SVG NODE NOT IMPLEMENTED: "
235 << info.nodeId
236 << " type: " << info.typeName;
237}
238
239void QQuickItemGenerator::generateTextNode(const TextNodeInfo &info)
240{
241 if (!isNodeVisible(info))
242 return;
243
244 QQuickItem *alignItem = nullptr;
245 QQuickText *textItem = nullptr;
246
247 QQuickItem *containerItem = new QQuickItem(currentItem());
248 addCurrentItem(item: containerItem, info);
249
250 generateNodeBase(info);
251
252 if (!info.isTextArea) {
253 alignItem = new QQuickItem(currentItem());
254 alignItem->setX(info.position.x());
255 alignItem->setY(info.position.y());
256 }
257
258 textItem = new QQuickText(containerItem);
259 addCurrentItem(item: textItem, info);
260
261 if (info.isTextArea) {
262 textItem->setX(info.position.x());
263 textItem->setY(info.position.y());
264 if (info.size.width() > 0)
265 textItem->setWidth(info.size.width());
266 if (info.size.height() > 0)
267 textItem->setHeight(info.size.height());
268 textItem->setWrapMode(QQuickText::Wrap);
269 textItem->setClip(true);
270 } else {
271 auto *anchors = QQuickItemPrivate::get(item: textItem)->anchors();
272 auto *alignPrivate = QQuickItemPrivate::get(item: alignItem);
273 anchors->setBaseline(alignPrivate->top());
274
275 switch (info.alignment) {
276 case Qt::AlignHCenter:
277 anchors->setHorizontalCenter(alignPrivate->left());
278 break;
279 case Qt::AlignRight:
280 anchors->setRight(alignPrivate->left());
281 break;
282 default:
283 qCDebug(lcQuickVectorImage) << "Unexpected text alignment" << info.alignment;
284 Q_FALLTHROUGH();
285 case Qt::AlignLeft:
286 anchors->setLeft(alignPrivate->left());
287 break;
288 }
289 }
290
291 textItem->setColor(info.fillColor);
292 textItem->setTextFormat(info.needsRichText ? QQuickText::RichText : QQuickText::StyledText);
293 textItem->setText(info.text);
294 textItem->setFont(info.font);
295
296 if (info.strokeColor != QColorConstants::Transparent) {
297 textItem->setStyleColor(info.strokeColor);
298 textItem->setStyle(QQuickText::Outline);
299 }
300
301 m_items.pop(); m_items.pop();
302}
303
304void QQuickItemGenerator::generateUseNode(const UseNodeInfo &info)
305{
306 if (!isNodeVisible(info))
307 return;
308
309 if (info.stage == StructureNodeStage::Start) {
310 QQuickItem *item = new QQuickItem();
311 item->setPosition(info.startPos);
312 addCurrentItem(item, info);
313 generateNodeBase(info);
314 } else {
315 m_items.pop();
316 }
317
318}
319
320void QQuickItemGenerator::generatePathContainer(const StructureNodeInfo &info)
321{
322 m_inShapeItem = true;
323 auto *shapeItem = new QQuickShape;
324 if (m_flags.testFlag(flag: QQuickVectorImageGenerator::GeneratorFlag::CurveRenderer))
325 shapeItem->setPreferredRendererType(QQuickShape::CurveRenderer);
326 m_parentShapeItem = shapeItem;
327 addCurrentItem(item: shapeItem, info);
328}
329
330bool QQuickItemGenerator::generateStructureNode(const StructureNodeInfo &info)
331{
332 if (!isNodeVisible(info))
333 return false;
334
335 if (info.stage == StructureNodeStage::Start) {
336 if (!info.forceSeparatePaths && info.isPathContainer) {
337 generatePathContainer(info);
338 } else {
339 QQuickItem *item = !info.viewBox.isEmpty() ? new QQuickVectorImageGenerator::Utils::ViewBoxItem(info.viewBox) : new QQuickItem;
340 addCurrentItem(item, info);
341 }
342
343 generateNodeBase(info);
344 } else {
345 m_inShapeItem = false;
346 m_parentShapeItem = nullptr;
347 m_items.pop();
348 }
349
350 return true;
351}
352
353bool QQuickItemGenerator::generateRootNode(const StructureNodeInfo &info)
354{
355 if (!isNodeVisible(info)) {
356 QQuickItem *item = new QQuickItem();
357 item->setParentItem(m_parentItem);
358
359 if (info.size.width() > 0)
360 m_parentItem->setImplicitWidth(info.size.width());
361
362 if (info.size.height() > 0)
363 m_parentItem->setImplicitHeight(info.size.height());
364
365 item->setWidth(m_parentItem->implicitWidth());
366 item->setHeight(m_parentItem->implicitHeight());
367
368 return false;
369 }
370
371 if (info.stage == StructureNodeStage::Start) {
372 QQuickItem *item = !info.viewBox.isEmpty() ? new QQuickVectorImageGenerator::Utils::ViewBoxItem(info.viewBox) : new QQuickItem;
373 addCurrentItem(item, info);
374 if (info.size.width() > 0)
375 m_parentItem->setImplicitWidth(info.size.width());
376
377 if (info.size.height() > 0)
378 m_parentItem->setImplicitHeight(info.size.height());
379
380 item->setWidth(m_parentItem->implicitWidth());
381 item->setHeight(m_parentItem->implicitHeight());
382 generateNodeBase(info);
383
384 if (!info.forceSeparatePaths && info.isPathContainer)
385 generatePathContainer(info);
386 } else {
387 if (m_inShapeItem) {
388 m_inShapeItem = false;
389 m_parentShapeItem = nullptr;
390 m_items.pop();
391 }
392
393 m_items.pop();
394 }
395
396 return true;
397}
398
399QQuickItem *QQuickItemGenerator::currentItem()
400{
401 return m_items.top();
402}
403
404void QQuickItemGenerator::addCurrentItem(QQuickItem *item, const NodeInfo &info)
405{
406 item->setParentItem(currentItem());
407 m_items.push(t: item);
408 QStringView name = !info.nodeId.isEmpty() ? info.nodeId : info.typeName;
409 item->setObjectName(name);
410}
411
412QT_END_NAMESPACE
413

source code of qtdeclarative/src/quickvectorimage/generator/qquickitemgenerator.cpp