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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage) |
19 | |
20 | QQuickItemGenerator::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 | |
28 | QQuickItemGenerator::~QQuickItemGenerator() |
29 | { |
30 | } |
31 | |
32 | void 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 | |
64 | bool QQuickItemGenerator::generateDefsNode(const NodeInfo &info) |
65 | { |
66 | Q_UNUSED(info) |
67 | |
68 | return false; |
69 | } |
70 | |
71 | void 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 | |
91 | void 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 | |
119 | void 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 | |
189 | void 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 | |
229 | void 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 | |
239 | void 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 | |
304 | void 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 | |
320 | void 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 | |
330 | bool 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 | |
353 | bool 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 | |
399 | QQuickItem *QQuickItemGenerator::currentItem() |
400 | { |
401 | return m_items.top(); |
402 | } |
403 | |
404 | void 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 | |
412 | QT_END_NAMESPACE |
413 | |