| 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 "qsvgvisitorimpl_p.h" | 
| 5 | #include "qquickgenerator_p.h" | 
| 6 | #include "qquicknodeinfo_p.h" | 
| 7 |  | 
| 8 | #include <private/qsvgvisitor_p.h> | 
| 9 |  | 
| 10 | #include <QString> | 
| 11 | #include <QPainter> | 
| 12 | #include <QTextDocument> | 
| 13 | #include <QTextLayout> | 
| 14 | #include <QMatrix4x4> | 
| 15 | #include <QQuickItem> | 
| 16 |  | 
| 17 | #include <private/qquickshape_p.h> | 
| 18 | #include <private/qquicktext_p.h> | 
| 19 | #include <private/qquicktranslate_p.h> | 
| 20 | #include <private/qquickitem_p.h> | 
| 21 |  | 
| 22 | #include <private/qquickimagebase_p_p.h> | 
| 23 | #include <private/qquickimage_p.h> | 
| 24 | #include <private/qsgcurveprocessor_p.h> | 
| 25 |  | 
| 26 | #include <private/qquadpath_p.h> | 
| 27 |  | 
| 28 | #include <QtCore/private/qstringiterator_p.h> | 
| 29 |  | 
| 30 | #include "utils_p.h" | 
| 31 | #include <QtCore/qloggingcategory.h> | 
| 32 |  | 
| 33 | #include <QtSvg/private/qsvgstyle_p.h> | 
| 34 |  | 
| 35 | QT_BEGIN_NAMESPACE | 
| 36 |  | 
| 37 | using namespace Qt::StringLiterals; | 
| 38 |  | 
| 39 | Q_DECLARE_LOGGING_CATEGORY(lcQuickVectorImage) | 
| 40 |  | 
| 41 | class QSvgStyleResolver | 
| 42 | { | 
| 43 | public: | 
| 44 |     QSvgStyleResolver() | 
| 45 |     { | 
| 46 |         m_dummyImage = QImage(1, 1, QImage::Format_RGB32); | 
| 47 |         m_dummyPainter.begin(&m_dummyImage); | 
| 48 |         QPen defaultPen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin); | 
| 49 |         defaultPen.setMiterLimit(4); | 
| 50 |         m_dummyPainter.setPen(defaultPen); | 
| 51 |         m_dummyPainter.setBrush(Qt::black); | 
| 52 |     } | 
| 53 |  | 
| 54 |     ~QSvgStyleResolver() | 
| 55 |     { | 
| 56 |         m_dummyPainter.end(); | 
| 57 |     } | 
| 58 |  | 
| 59 |     QPainter& painter() { return m_dummyPainter; } | 
| 60 |     QSvgExtraStates& states() { return m_svgState; } | 
| 61 |  | 
| 62 |     QColor currentFillColor() const | 
| 63 |     { | 
| 64 |         if (m_dummyPainter.brush().style() == Qt::NoBrush || | 
| 65 |             m_dummyPainter.brush().color() == QColorConstants::Transparent) { | 
| 66 |             return QColor(QColorConstants::Transparent); | 
| 67 |         } | 
| 68 |  | 
| 69 |         QColor fillColor; | 
| 70 |         fillColor = m_dummyPainter.brush().color(); | 
| 71 |         fillColor.setAlphaF(m_svgState.fillOpacity); | 
| 72 |  | 
| 73 |         return fillColor; | 
| 74 |     } | 
| 75 |  | 
| 76 |     qreal currentFillOpacity() const | 
| 77 |     { | 
| 78 |         return m_svgState.fillOpacity; | 
| 79 |     } | 
| 80 |  | 
| 81 |     const QGradient *currentStrokeGradient() const | 
| 82 |     { | 
| 83 |         QBrush brush = m_dummyPainter.pen().brush(); | 
| 84 |         if (brush.style() == Qt::LinearGradientPattern | 
| 85 |                 || brush.style() == Qt::RadialGradientPattern | 
| 86 |                 || brush.style() == Qt::ConicalGradientPattern) { | 
| 87 |             return brush.gradient(); | 
| 88 |         } | 
| 89 |         return nullptr; | 
| 90 |     } | 
| 91 |  | 
| 92 |     const QGradient *currentFillGradient() const | 
| 93 |     { | 
| 94 |         if (m_dummyPainter.brush().style() == Qt::LinearGradientPattern || m_dummyPainter.brush().style() == Qt::RadialGradientPattern || m_dummyPainter.brush().style() == Qt::ConicalGradientPattern ) | 
| 95 |             return m_dummyPainter.brush().gradient(); | 
| 96 |         return nullptr; | 
| 97 |     } | 
| 98 |  | 
| 99 |     QTransform currentFillTransform() const | 
| 100 |     { | 
| 101 |         return m_dummyPainter.brush().transform(); | 
| 102 |     } | 
| 103 |  | 
| 104 |     QColor currentStrokeColor() const | 
| 105 |     { | 
| 106 |         if (m_dummyPainter.pen().brush().style() == Qt::NoBrush || | 
| 107 |             m_dummyPainter.pen().brush().color() == QColorConstants::Transparent) { | 
| 108 |             return QColor(QColorConstants::Transparent); | 
| 109 |         } | 
| 110 |  | 
| 111 |         QColor strokeColor; | 
| 112 |         strokeColor = m_dummyPainter.pen().brush().color(); | 
| 113 |         strokeColor.setAlphaF(m_svgState.strokeOpacity); | 
| 114 |  | 
| 115 |         return strokeColor; | 
| 116 |     } | 
| 117 |  | 
| 118 |     static QGradient applyOpacityToGradient(const QGradient &gradient, float opacity) | 
| 119 |     { | 
| 120 |         QGradient grad = gradient; | 
| 121 |         QGradientStops stops; | 
| 122 |         for (auto &stop : grad.stops()) { | 
| 123 |             stop.second.setAlphaF(stop.second.alphaF() * opacity); | 
| 124 |             stops.append(t: stop); | 
| 125 |         } | 
| 126 |  | 
| 127 |         grad.setStops(stops); | 
| 128 |  | 
| 129 |         return grad; | 
| 130 |     } | 
| 131 |  | 
| 132 |     float currentStrokeWidth() const | 
| 133 |     { | 
| 134 |         float penWidth = m_dummyPainter.pen().widthF(); | 
| 135 |         return penWidth ? penWidth : 1; | 
| 136 |     } | 
| 137 |  | 
| 138 |     QPen currentStroke() const | 
| 139 |     { | 
| 140 |         return m_dummyPainter.pen(); | 
| 141 |     } | 
| 142 |  | 
| 143 | protected: | 
| 144 |     QPainter m_dummyPainter; | 
| 145 |     QImage m_dummyImage; | 
| 146 |     QSvgExtraStates m_svgState; | 
| 147 | }; | 
| 148 |  | 
| 149 | Q_GLOBAL_STATIC(QSvgStyleResolver, styleResolver) | 
| 150 |  | 
| 151 | namespace { | 
| 152 | inline bool isPathContainer(const QSvgStructureNode *node) | 
| 153 | { | 
| 154 |     bool foundPath = false; | 
| 155 |     for (const auto *child : node->renderers()) { | 
| 156 |         switch (child->type()) { | 
| 157 |             // nodes that shouldn't go inside Shape{} | 
| 158 |         case QSvgNode::Switch: | 
| 159 |         case QSvgNode::Doc: | 
| 160 |         case QSvgNode::Group: | 
| 161 |         case QSvgNode::Animation: | 
| 162 |         case QSvgNode::Use: | 
| 163 |         case QSvgNode::Video: | 
| 164 |         case QSvgNode::Image: | 
| 165 |         case QSvgNode::Textarea: | 
| 166 |         case QSvgNode::Text: | 
| 167 |         case QSvgNode::Tspan: | 
| 168 |             //qCDebug(lcQuickVectorGraphics) << "NOT path container because" << node->typeName() ; | 
| 169 |             return false; | 
| 170 |  | 
| 171 |             // nodes that could go inside Shape{} | 
| 172 |         case QSvgNode::Defs: | 
| 173 |             break; | 
| 174 |  | 
| 175 |             // nodes that are done as pure ShapePath{} | 
| 176 |         case QSvgNode::Rect: | 
| 177 |         case QSvgNode::Circle: | 
| 178 |         case QSvgNode::Ellipse: | 
| 179 |         case QSvgNode::Line: | 
| 180 |         case QSvgNode::Path: | 
| 181 |         case QSvgNode::Polygon: | 
| 182 |         case QSvgNode::Polyline: | 
| 183 |             if (!child->style().transform.isDefault()) { | 
| 184 |                 //qCDebug(lcQuickVectorGraphics) << "NOT path container because local transform"; | 
| 185 |                 return false; | 
| 186 |             } | 
| 187 |             foundPath = true; | 
| 188 |             break; | 
| 189 |         default: | 
| 190 |             qCDebug(lcQuickVectorImage) << "Unhandled type in switch"  << child->type(); | 
| 191 |             break; | 
| 192 |         } | 
| 193 |     } | 
| 194 |     //qCDebug(lcQuickVectorGraphics) << "Container" << node->nodeId() << node->typeName()  << "is" << foundPath; | 
| 195 |     return foundPath; | 
| 196 | } | 
| 197 |  | 
| 198 | static QString capStyleName(Qt::PenCapStyle style) | 
| 199 | { | 
| 200 |     QString styleName; | 
| 201 |  | 
| 202 |     switch (style) { | 
| 203 |     case Qt::SquareCap: | 
| 204 |         styleName = QStringLiteral("squarecap" ); | 
| 205 |         break; | 
| 206 |     case Qt::FlatCap: | 
| 207 |         styleName = QStringLiteral("flatcap" ); | 
| 208 |         break; | 
| 209 |     case Qt::RoundCap: | 
| 210 |         styleName = QStringLiteral("roundcap" ); | 
| 211 |         break; | 
| 212 |     default: | 
| 213 |         break; | 
| 214 |     } | 
| 215 |  | 
| 216 |     return styleName; | 
| 217 | } | 
| 218 |  | 
| 219 | static QString joinStyleName(Qt::PenJoinStyle style) | 
| 220 | { | 
| 221 |     QString styleName; | 
| 222 |  | 
| 223 |     switch (style) { | 
| 224 |     case Qt::MiterJoin: | 
| 225 |         styleName = QStringLiteral("miterjoin" ); | 
| 226 |         break; | 
| 227 |     case Qt::BevelJoin: | 
| 228 |         styleName = QStringLiteral("beveljoin" ); | 
| 229 |         break; | 
| 230 |     case Qt::RoundJoin: | 
| 231 |         styleName = QStringLiteral("roundjoin" ); | 
| 232 |         break; | 
| 233 |     case Qt::SvgMiterJoin: | 
| 234 |         styleName = QStringLiteral("svgmiterjoin" ); | 
| 235 |         break; | 
| 236 |     default: | 
| 237 |         break; | 
| 238 |     } | 
| 239 |  | 
| 240 |     return styleName; | 
| 241 | } | 
| 242 |  | 
| 243 | static QString dashArrayString(QList<qreal> dashArray) | 
| 244 | { | 
| 245 |     if (dashArray.isEmpty()) | 
| 246 |         return QString(); | 
| 247 |  | 
| 248 |     QString dashArrayString; | 
| 249 |     QTextStream stream(&dashArrayString); | 
| 250 |  | 
| 251 |     for (int i = 0; i < dashArray.length() - 1; i++) { | 
| 252 |         qreal value = dashArray[i]; | 
| 253 |         stream << value << ", " ; | 
| 254 |     } | 
| 255 |  | 
| 256 |     stream << dashArray.last(); | 
| 257 |  | 
| 258 |     return dashArrayString; | 
| 259 | } | 
| 260 | }; | 
| 261 |  | 
| 262 | QSvgVisitorImpl::QSvgVisitorImpl(const QString svgFileName, QQuickGenerator *generator) | 
| 263 |     : m_svgFileName(svgFileName) | 
| 264 |     , m_generator(generator) | 
| 265 | { | 
| 266 | } | 
| 267 |  | 
| 268 | bool QSvgVisitorImpl::traverse() | 
| 269 | { | 
| 270 |     if (!m_generator) { | 
| 271 |         qCDebug(lcQuickVectorImage) << "No valid QQuickGenerator is set. Genration will stop" ; | 
| 272 |         return false; | 
| 273 |     } | 
| 274 |  | 
| 275 |     auto *doc = QSvgTinyDocument::load(file: m_svgFileName); | 
| 276 |     if (!doc) { | 
| 277 |         qCDebug(lcQuickVectorImage) << "Not a valid Svg File : "  << m_svgFileName; | 
| 278 |         return false; | 
| 279 |     } | 
| 280 |  | 
| 281 |     QSvgVisitor::traverse(node: doc); | 
| 282 |     return true; | 
| 283 | } | 
| 284 |  | 
| 285 | void QSvgVisitorImpl::visitNode(const QSvgNode *node) | 
| 286 | { | 
| 287 |     handleBaseNodeSetup(node); | 
| 288 |  | 
| 289 |     NodeInfo info; | 
| 290 |     fillCommonNodeInfo(node, info); | 
| 291 |  | 
| 292 |     m_generator->generateNode(info); | 
| 293 |  | 
| 294 |     handleBaseNodeEnd(node); | 
| 295 | } | 
| 296 |  | 
| 297 | void QSvgVisitorImpl::visitImageNode(const QSvgImage *node) | 
| 298 | { | 
| 299 |     // TODO: this requires proper asset management. | 
| 300 |     handleBaseNodeSetup(node); | 
| 301 |  | 
| 302 |     ImageNodeInfo info; | 
| 303 |     fillCommonNodeInfo(node, info); | 
| 304 |     info.image = node->image(); | 
| 305 |     info.rect = node->rect(); | 
| 306 |     info.externalFileReference = node->filename(); | 
| 307 |  | 
| 308 |     m_generator->generateImageNode(info); | 
| 309 |  | 
| 310 |     handleBaseNodeEnd(node); | 
| 311 | } | 
| 312 |  | 
| 313 | void QSvgVisitorImpl::visitRectNode(const QSvgRect *node) | 
| 314 | { | 
| 315 |     QRectF rect = node->rect(); | 
| 316 |     QPointF rads = node->radius(); | 
| 317 |     // This is using Qt::RelativeSize semantics: percentage of half rect size | 
| 318 |     qreal x1 = rect.left(); | 
| 319 |     qreal x2 = rect.right(); | 
| 320 |     qreal y1 = rect.top(); | 
| 321 |     qreal y2 = rect.bottom(); | 
| 322 |  | 
| 323 |     qreal rx =  rads.x() * rect.width() / 200; | 
| 324 |     qreal ry = rads.y() * rect.height() / 200; | 
| 325 |     QPainterPath p; | 
| 326 |  | 
| 327 |     p.moveTo(x: x1 + rx, y: y1); | 
| 328 |     p.lineTo(x: x2 - rx, y: y1); | 
| 329 |     // qCDebug(lcQuickVectorGraphics) << "Line1" << x2 - rx << y1; | 
| 330 |     p.arcTo(x: x2 - rx * 2, y: y1, w: rx * 2, h: ry * 2, startAngle: 90, arcLength: -90); // ARC to x2, y1 + ry | 
| 331 |     // qCDebug(lcQuickVectorGraphics) << "p1" << p; | 
| 332 |  | 
| 333 |     p.lineTo(x: x2, y: y2 - ry); | 
| 334 |     p.arcTo(x: x2 - rx * 2, y: y2 - ry * 2, w: rx * 2, h: ry * 2, startAngle: 0, arcLength: -90); // ARC to x2 - rx, y2 | 
| 335 |  | 
| 336 |     p.lineTo(x: x1 + rx, y: y2); | 
| 337 |     p.arcTo(x: x1, y: y2 - ry * 2, w: rx * 2, h: ry * 2, startAngle: 270, arcLength: -90); // ARC to x1, y2 - ry | 
| 338 |  | 
| 339 |     p.lineTo(x: x1, y: y1 + ry); | 
| 340 |     p.arcTo(x: x1, y: y1, w: rx * 2, h: ry * 2, startAngle: 180, arcLength: -90); // ARC to x1 + rx, y1 | 
| 341 |  | 
| 342 |     handlePathNode(node, path: p); | 
| 343 | } | 
| 344 |  | 
| 345 | void QSvgVisitorImpl::visitEllipseNode(const QSvgEllipse *node) | 
| 346 | { | 
| 347 |     QRectF rect = node->rect(); | 
| 348 |  | 
| 349 |     QPainterPath p; | 
| 350 |     p.addEllipse(rect); | 
| 351 |  | 
| 352 |     handlePathNode(node, path: p); | 
| 353 | } | 
| 354 |  | 
| 355 | void QSvgVisitorImpl::visitPathNode(const QSvgPath *node) | 
| 356 | { | 
| 357 |     handlePathNode(node, path: node->path()); | 
| 358 | } | 
| 359 |  | 
| 360 | void QSvgVisitorImpl::visitLineNode(const QSvgLine *node) | 
| 361 | { | 
| 362 |     QPainterPath p; | 
| 363 |     p.moveTo(p: node->line().p1()); | 
| 364 |     p.lineTo(p: node->line().p2()); | 
| 365 |     handlePathNode(node, path: p); | 
| 366 | } | 
| 367 |  | 
| 368 | void QSvgVisitorImpl::visitPolygonNode(const QSvgPolygon *node) | 
| 369 | { | 
| 370 |     QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(poly: node->polygon(), closed: true); | 
| 371 |     handlePathNode(node, path: p); | 
| 372 | } | 
| 373 |  | 
| 374 | void QSvgVisitorImpl::visitPolylineNode(const QSvgPolyline *node) | 
| 375 | { | 
| 376 |     QPainterPath p = QQuickVectorImageGenerator::Utils::polygonToPath(poly: node->polygon(), closed: false); | 
| 377 |     handlePathNode(node, path: p); | 
| 378 | } | 
| 379 |  | 
| 380 | QString QSvgVisitorImpl::gradientCssDescription(const QGradient *gradient) | 
| 381 | { | 
| 382 |     QString cssDescription; | 
| 383 |     if (gradient->type() == QGradient::LinearGradient) { | 
| 384 |         const QLinearGradient *linearGradient = static_cast<const QLinearGradient *>(gradient); | 
| 385 |  | 
| 386 |         cssDescription += " -qt-foreground: qlineargradient("_L1 ; | 
| 387 |         cssDescription += "x1:"_L1  + QString::number(linearGradient->start().x()) + u','; | 
| 388 |         cssDescription += "y1:"_L1  + QString::number(linearGradient->start().y()) + u','; | 
| 389 |         cssDescription += "x2:"_L1  + QString::number(linearGradient->finalStop().x()) + u','; | 
| 390 |         cssDescription += "y2:"_L1  + QString::number(linearGradient->finalStop().y()) + u','; | 
| 391 |     } else if (gradient->type() == QGradient::RadialGradient) { | 
| 392 |         const QRadialGradient *radialGradient = static_cast<const QRadialGradient *>(gradient); | 
| 393 |  | 
| 394 |         cssDescription += " -qt-foreground: qradialgradient("_L1 ; | 
| 395 |         cssDescription += "cx:"_L1  + QString::number(radialGradient->center().x()) + u','; | 
| 396 |         cssDescription += "cy:"_L1  + QString::number(radialGradient->center().y()) + u','; | 
| 397 |         cssDescription += "fx:"_L1  + QString::number(radialGradient->focalPoint().x()) + u','; | 
| 398 |         cssDescription += "fy:"_L1  + QString::number(radialGradient->focalPoint().y()) + u','; | 
| 399 |         cssDescription += "radius:"_L1  + QString::number(radialGradient->radius()) + u','; | 
| 400 |     } else { | 
| 401 |         const QConicalGradient *conicalGradient = static_cast<const QConicalGradient *>(gradient); | 
| 402 |  | 
| 403 |         cssDescription += " -qt-foreground: qconicalgradient("_L1 ; | 
| 404 |         cssDescription += "cx:"_L1  + QString::number(conicalGradient->center().x()) + u','; | 
| 405 |         cssDescription += "cy:"_L1  + QString::number(conicalGradient->center().y()) + u','; | 
| 406 |         cssDescription += "angle:"_L1  + QString::number(conicalGradient->angle()) + u','; | 
| 407 |     } | 
| 408 |  | 
| 409 |     const QStringList coordinateModes = { "logical"_L1 , "stretchtodevice"_L1 , "objectbounding"_L1 , "object"_L1  }; | 
| 410 |     cssDescription += "coordinatemode:"_L1 ; | 
| 411 |     cssDescription += coordinateModes.at(i: int(gradient->coordinateMode())); | 
| 412 |     cssDescription += u','; | 
| 413 |  | 
| 414 |     const QStringList spreads = { "pad"_L1 , "reflect"_L1 , "repeat"_L1  }; | 
| 415 |     cssDescription += "spread:"_L1 ; | 
| 416 |     cssDescription += spreads.at(i: int(gradient->spread())); | 
| 417 |  | 
| 418 |     for (const QGradientStop &stop : gradient->stops()) { | 
| 419 |         cssDescription += ",stop:"_L1 ; | 
| 420 |         cssDescription += QString::number(stop.first); | 
| 421 |         cssDescription += u' '; | 
| 422 |         cssDescription += stop.second.name(format: QColor::HexArgb); | 
| 423 |     } | 
| 424 |  | 
| 425 |     cssDescription += ");"_L1 ; | 
| 426 |  | 
| 427 |     return cssDescription; | 
| 428 | } | 
| 429 |  | 
| 430 | QString QSvgVisitorImpl::colorCssDescription(QColor color) | 
| 431 | { | 
| 432 |     QString cssDescription; | 
| 433 |     cssDescription += QStringLiteral("rgba(" ); | 
| 434 |     cssDescription += QString::number(color.red()) + QStringLiteral("," ); | 
| 435 |     cssDescription += QString::number(color.green()) + QStringLiteral("," ); | 
| 436 |     cssDescription += QString::number(color.blue()) + QStringLiteral("," ); | 
| 437 |     cssDescription += QString::number(color.alphaF()) + QStringLiteral(")" ); | 
| 438 |  | 
| 439 |     return cssDescription; | 
| 440 | } | 
| 441 |  | 
| 442 | namespace { | 
| 443 |  | 
| 444 |     // Simple class for representing the SVG font as a font engine | 
| 445 |     // We use the Proxy font engine type, which is currently unused and does not map to | 
| 446 |     // any specific font engine | 
| 447 |     // (The QSvgFont object must outlive the engine.) | 
| 448 |     class QSvgFontEngine : public QFontEngine | 
| 449 |     { | 
| 450 |     public: | 
| 451 |         QSvgFontEngine(const QSvgFont *font, qreal size); | 
| 452 |  | 
| 453 |         QFontEngine *cloneWithSize(qreal size) const override; | 
| 454 |  | 
| 455 |         glyph_t glyphIndex(uint ucs4) const override; | 
| 456 |         int stringToCMap(const QChar *str, | 
| 457 |                          int len, | 
| 458 |                          QGlyphLayout *glyphs, | 
| 459 |                          int *nglyphs, | 
| 460 |                          ShaperFlags flags) const override; | 
| 461 |  | 
| 462 |         void addGlyphsToPath(glyph_t *glyphs, | 
| 463 |                              QFixedPoint *positions, | 
| 464 |                              int nGlyphs, | 
| 465 |                              QPainterPath *path, | 
| 466 |                              QTextItem::RenderFlags flags) override; | 
| 467 |  | 
| 468 |         glyph_metrics_t boundingBox(glyph_t glyph) override; | 
| 469 |  | 
| 470 |         void recalcAdvances(QGlyphLayout *, ShaperFlags) const override; | 
| 471 |         QFixed ascent() const override; | 
| 472 |         QFixed capHeight() const override; | 
| 473 |         QFixed descent() const override; | 
| 474 |         QFixed leading() const override; | 
| 475 |         qreal maxCharWidth() const override; | 
| 476 |         qreal minLeftBearing() const override; | 
| 477 |         qreal minRightBearing() const override; | 
| 478 |  | 
| 479 |         QFixed emSquareSize() const override; | 
| 480 |  | 
| 481 |     private: | 
| 482 |         const QSvgFont *m_font; | 
| 483 |     }; | 
| 484 |  | 
| 485 |     QSvgFontEngine::QSvgFontEngine(const QSvgFont *font, qreal size) | 
| 486 |         : QFontEngine(Proxy) | 
| 487 |         , m_font(font) | 
| 488 |     { | 
| 489 |         fontDef.pixelSize = size; | 
| 490 |         fontDef.families = QStringList(m_font->m_familyName); | 
| 491 |     } | 
| 492 |  | 
| 493 |     QFixed QSvgFontEngine::emSquareSize() const | 
| 494 |     { | 
| 495 |         return QFixed::fromReal(r: m_font->m_unitsPerEm); | 
| 496 |     } | 
| 497 |  | 
| 498 |     glyph_t QSvgFontEngine::glyphIndex(uint ucs4) const | 
| 499 |     { | 
| 500 |         if (ucs4 < USHRT_MAX && m_font->m_glyphs.contains(key: QChar(ushort(ucs4)))) | 
| 501 |             return glyph_t(ucs4); | 
| 502 |  | 
| 503 |         return 0; | 
| 504 |     } | 
| 505 |  | 
| 506 |     int QSvgFontEngine::stringToCMap(const QChar *str, | 
| 507 |                                      int len, | 
| 508 |                                      QGlyphLayout *glyphs, | 
| 509 |                                      int *nglyphs, | 
| 510 |                                      ShaperFlags flags) const | 
| 511 |     { | 
| 512 |         Q_ASSERT(glyphs->numGlyphs >= *nglyphs); | 
| 513 |         if (*nglyphs < len) { | 
| 514 |             *nglyphs = len; | 
| 515 |             return -1; | 
| 516 |         } | 
| 517 |  | 
| 518 |         int ucs4Length = 0; | 
| 519 |         QStringIterator it(str, str + len); | 
| 520 |         while (it.hasNext()) { | 
| 521 |             char32_t ucs4 = it.next(); | 
| 522 |             glyph_t index = glyphIndex(ucs4); | 
| 523 |             glyphs->glyphs[ucs4Length++] = index; | 
| 524 |         } | 
| 525 |  | 
| 526 |         *nglyphs = ucs4Length; | 
| 527 |         glyphs->numGlyphs = ucs4Length; | 
| 528 |  | 
| 529 |         if (!(flags & GlyphIndicesOnly)) | 
| 530 |             recalcAdvances(glyphs, flags); | 
| 531 |  | 
| 532 |         return *nglyphs; | 
| 533 |     } | 
| 534 |  | 
| 535 |     void QSvgFontEngine::addGlyphsToPath(glyph_t *glyphs, | 
| 536 |                                          QFixedPoint *positions, | 
| 537 |                                          int nGlyphs, | 
| 538 |                                          QPainterPath *path, | 
| 539 |                                          QTextItem::RenderFlags flags) | 
| 540 |     { | 
| 541 |         Q_UNUSED(flags); | 
| 542 |         const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm; | 
| 543 |         for (int i = 0; i < nGlyphs; ++i) { | 
| 544 |             glyph_t index = glyphs[i]; | 
| 545 |             if (index > 0) { | 
| 546 |                 QPointF position = positions[i].toPointF(); | 
| 547 |                 QPainterPath glyphPath = m_font->m_glyphs.value(key: QChar(ushort(index))).m_path; | 
| 548 |  | 
| 549 |                 QTransform xform; | 
| 550 |                 xform.translate(dx: position.x(), dy: position.y()); | 
| 551 |                 xform.scale(sx: scale, sy: -scale); | 
| 552 |                 glyphPath = xform.map(p: glyphPath); | 
| 553 |                 path->addPath(path: glyphPath); | 
| 554 |             } | 
| 555 |         } | 
| 556 |     } | 
| 557 |  | 
| 558 |     glyph_metrics_t QSvgFontEngine::boundingBox(glyph_t glyph) | 
| 559 |     { | 
| 560 |         glyph_metrics_t ret; | 
| 561 |         ret.x = 0; // left bearing | 
| 562 |         ret.y = -ascent(); | 
| 563 |         const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm; | 
| 564 |         const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(key: QChar(ushort(glyph))); | 
| 565 |         ret.width = QFixed::fromReal(r: svgGlyph.m_horizAdvX * scale); | 
| 566 |         ret.height = ascent() + descent(); | 
| 567 |         return ret; | 
| 568 |     } | 
| 569 |  | 
| 570 |     QFontEngine *QSvgFontEngine::cloneWithSize(qreal size) const | 
| 571 |     { | 
| 572 |         QSvgFontEngine *otherEngine = new QSvgFontEngine(m_font, size); | 
| 573 |         return otherEngine; | 
| 574 |     } | 
| 575 |  | 
| 576 |     void QSvgFontEngine::recalcAdvances(QGlyphLayout *glyphLayout, ShaperFlags) const | 
| 577 |     { | 
| 578 |         const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm; | 
| 579 |         for (int i = 0; i < glyphLayout->numGlyphs; i++) { | 
| 580 |             glyph_t glyph = glyphLayout->glyphs[i]; | 
| 581 |             const QSvgGlyph &svgGlyph = m_font->m_glyphs.value(key: QChar(ushort(glyph))); | 
| 582 |             glyphLayout->advances[i] = QFixed::fromReal(r: svgGlyph.m_horizAdvX * scale); | 
| 583 |         } | 
| 584 |     } | 
| 585 |  | 
| 586 |     QFixed QSvgFontEngine::ascent() const | 
| 587 |     { | 
| 588 |         return QFixed::fromReal(r: fontDef.pixelSize); | 
| 589 |     } | 
| 590 |  | 
| 591 |     QFixed QSvgFontEngine::capHeight() const | 
| 592 |     { | 
| 593 |         return ascent(); | 
| 594 |     } | 
| 595 |     QFixed QSvgFontEngine::descent() const | 
| 596 |     { | 
| 597 |         return QFixed{}; | 
| 598 |     } | 
| 599 |  | 
| 600 |     QFixed QSvgFontEngine::leading() const | 
| 601 |     { | 
| 602 |         return QFixed{}; | 
| 603 |     } | 
| 604 |  | 
| 605 |     qreal QSvgFontEngine::maxCharWidth() const | 
| 606 |     { | 
| 607 |         const qreal scale = fontDef.pixelSize / m_font->m_unitsPerEm; | 
| 608 |         return m_font->m_horizAdvX * scale; | 
| 609 |     } | 
| 610 |  | 
| 611 |     qreal QSvgFontEngine::minLeftBearing() const | 
| 612 |     { | 
| 613 |         return 0.0; | 
| 614 |     } | 
| 615 |  | 
| 616 |     qreal QSvgFontEngine::minRightBearing() const | 
| 617 |     { | 
| 618 |         return 0.0; | 
| 619 |     } | 
| 620 | } | 
| 621 |  | 
| 622 | void QSvgVisitorImpl::visitTextNode(const QSvgText *node) | 
| 623 | { | 
| 624 |     handleBaseNodeSetup(node); | 
| 625 |     const bool isTextArea = node->type() == QSvgNode::Textarea; | 
| 626 |  | 
| 627 |     QString text; | 
| 628 |     const QSvgFont *svgFont = styleResolver->states().svgFont; | 
| 629 |     bool needsRichText = false; | 
| 630 |     bool preserveWhiteSpace = node->whitespaceMode() == QSvgText::Preserve; | 
| 631 |     const QGradient *mainGradient = styleResolver->currentFillGradient(); | 
| 632 |  | 
| 633 |     QFontEngine *fontEngine = nullptr; | 
| 634 |     if (svgFont != nullptr) { | 
| 635 |         fontEngine = new QSvgFontEngine(svgFont, styleResolver->painter().font().pointSize()); | 
| 636 |         fontEngine->ref.ref(); | 
| 637 |     } | 
| 638 |  | 
| 639 | #if QT_CONFIG(texthtmlparser) | 
| 640 |     bool needsPathNode = mainGradient != nullptr | 
| 641 |                            || svgFont != nullptr | 
| 642 |                            || styleResolver->currentStrokeGradient() != nullptr; | 
| 643 | #endif | 
| 644 |     for (const auto *tspan : node->tspans()) { | 
| 645 |         if (!tspan) { | 
| 646 |             text += QStringLiteral("<br>" ); | 
| 647 |             continue; | 
| 648 |         } | 
| 649 |  | 
| 650 |         // Note: We cannot get the font directly from the style, since this does | 
| 651 |         // not apply the weight, since this is relative and depends on current state. | 
| 652 |         handleBaseNodeSetup(node: tspan); | 
| 653 |         QFont font = styleResolver->painter().font(); | 
| 654 |  | 
| 655 |         QString styleTagContent; | 
| 656 |  | 
| 657 |         if ((font.resolveMask() & QFont::FamilyResolved) | 
| 658 |             || (font.resolveMask() & QFont::FamiliesResolved)) { | 
| 659 |             styleTagContent += QStringLiteral("font-family: %1;" ).arg(a: font.family()); | 
| 660 |         } | 
| 661 |  | 
| 662 |         if (font.resolveMask() & QFont::WeightResolved | 
| 663 |             && font.weight() != QFont::Normal | 
| 664 |             && font.weight() != QFont::Bold) { | 
| 665 |             styleTagContent += QStringLiteral("font-weight: %1;" ).arg(a: int(font.weight())); | 
| 666 |         } | 
| 667 |  | 
| 668 |         if (font.resolveMask() & QFont::SizeResolved) { | 
| 669 |             // Pixel size stored as point size in SVG parser | 
| 670 |             styleTagContent += QStringLiteral("font-size: %1px;" ).arg(a: int(font.pointSizeF())); | 
| 671 |         } | 
| 672 |  | 
| 673 |         if (font.resolveMask() & QFont::CapitalizationResolved | 
| 674 |             && font.capitalization() == QFont::SmallCaps) { | 
| 675 |             styleTagContent += QStringLiteral("font-variant: small-caps;" ); | 
| 676 |         } | 
| 677 |  | 
| 678 |         if (styleResolver->currentFillGradient() != nullptr | 
| 679 |             && styleResolver->currentFillGradient() != mainGradient) { | 
| 680 |             const QGradient grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity()); | 
| 681 |             styleTagContent += gradientCssDescription(gradient: &grad) + u';'; | 
| 682 | #if QT_CONFIG(texthtmlparser) | 
| 683 |             needsPathNode = true; | 
| 684 | #endif | 
| 685 |         } | 
| 686 |  | 
| 687 |         const QColor currentStrokeColor = styleResolver->currentStrokeColor(); | 
| 688 |         if (currentStrokeColor.alpha() > 0) { | 
| 689 |             QString strokeColor = colorCssDescription(color: currentStrokeColor); | 
| 690 |             styleTagContent += QStringLiteral("-qt-stroke-color:%1;" ).arg(a: strokeColor); | 
| 691 |             styleTagContent += QStringLiteral("-qt-stroke-width:%1px;" ).arg(a: styleResolver->currentStrokeWidth()); | 
| 692 |             styleTagContent += QStringLiteral("-qt-stroke-dasharray:%1;" ).arg(a: dashArrayString(dashArray: styleResolver->currentStroke().dashPattern())); | 
| 693 |             styleTagContent += QStringLiteral("-qt-stroke-dashoffset:%1;" ).arg(a: styleResolver->currentStroke().dashOffset()); | 
| 694 |             styleTagContent += QStringLiteral("-qt-stroke-lineCap:%1;" ).arg(a: capStyleName(style: styleResolver->currentStroke().capStyle())); | 
| 695 |             styleTagContent += QStringLiteral("-qt-stroke-lineJoin:%1;" ).arg(a: joinStyleName(style: styleResolver->currentStroke().joinStyle())); | 
| 696 |             if (styleResolver->currentStroke().joinStyle() == Qt::MiterJoin || styleResolver->currentStroke().joinStyle() == Qt::SvgMiterJoin) | 
| 697 |                 styleTagContent += QStringLiteral("-qt-stroke-miterlimit:%1;" ).arg(a: styleResolver->currentStroke().miterLimit()); | 
| 698 | #if QT_CONFIG(texthtmlparser) | 
| 699 |             needsPathNode = true; | 
| 700 | #endif | 
| 701 |         } | 
| 702 |  | 
| 703 |         if (tspan->whitespaceMode() == QSvgText::Preserve && !preserveWhiteSpace) | 
| 704 |             styleTagContent += QStringLiteral("white-space: pre-wrap;" ); | 
| 705 |  | 
| 706 |         QString content = tspan->text().toHtmlEscaped(); | 
| 707 |         content.replace(before: QLatin1Char('\t'), after: QLatin1Char(' ')); | 
| 708 |         content.replace(before: QLatin1Char('\n'), after: QLatin1Char(' ')); | 
| 709 |  | 
| 710 |         bool fontTag = false; | 
| 711 |         if (!tspan->style().fill.isDefault()) { | 
| 712 |             auto &b = tspan->style().fill->qbrush(); | 
| 713 |             qCDebug(lcQuickVectorImage) << "tspan FILL:"  << b; | 
| 714 |             if (b.style() != Qt::NoBrush) | 
| 715 |             { | 
| 716 |                 if (qFuzzyCompare(p1: b.color().alphaF() + 1.0, p2: 2.0)) | 
| 717 |                 { | 
| 718 |                     QString spanColor = b.color().name(); | 
| 719 |                     fontTag = !spanColor.isEmpty(); | 
| 720 |                     if (fontTag) | 
| 721 |                         text += QStringLiteral("<font color=\"%1\">" ).arg(a: spanColor); | 
| 722 |                 } else { | 
| 723 |                     QString spanColor = colorCssDescription(color: b.color()); | 
| 724 |                     styleTagContent += QStringLiteral("color:%1" ).arg(a: spanColor); | 
| 725 |                 } | 
| 726 |             } | 
| 727 |         } | 
| 728 |  | 
| 729 |         needsRichText = needsRichText || !styleTagContent.isEmpty(); | 
| 730 |         if (!styleTagContent.isEmpty()) | 
| 731 |             text += QStringLiteral("<span style=\"%1\">" ).arg(a: styleTagContent); | 
| 732 |  | 
| 733 |         if (font.resolveMask() & QFont::WeightResolved && font.bold()) | 
| 734 |             text += QStringLiteral("<b>" ); | 
| 735 |  | 
| 736 |         if (font.resolveMask() & QFont::StyleResolved && font.italic()) | 
| 737 |             text += QStringLiteral("<i>" ); | 
| 738 |  | 
| 739 |         if (font.resolveMask() & QFont::CapitalizationResolved) { | 
| 740 |             switch (font.capitalization()) { | 
| 741 |             case QFont::AllLowercase: | 
| 742 |                 content = content.toLower(); | 
| 743 |                 break; | 
| 744 |             case QFont::AllUppercase: | 
| 745 |                 content = content.toUpper(); | 
| 746 |                 break; | 
| 747 |             case QFont::Capitalize: | 
| 748 |                 // ### We need to iterate over the string and do the title case conversion, | 
| 749 |                 // since this is not part of QString. | 
| 750 |                 qCWarning(lcQuickVectorImage) << "Title case not implemented for tspan" ; | 
| 751 |                 break; | 
| 752 |             default: | 
| 753 |                 break; | 
| 754 |             } | 
| 755 |         } | 
| 756 |         text += content; | 
| 757 |         if (fontTag) | 
| 758 |             text += QStringLiteral("</font>" ); | 
| 759 |  | 
| 760 |         if (font.resolveMask() & QFont::StyleResolved && font.italic()) | 
| 761 |             text += QStringLiteral("</i>" ); | 
| 762 |  | 
| 763 |         if (font.resolveMask() & QFont::WeightResolved && font.bold()) | 
| 764 |             text += QStringLiteral("</b>" ); | 
| 765 |  | 
| 766 |         if (!styleTagContent.isEmpty()) | 
| 767 |             text += QStringLiteral("</span>" ); | 
| 768 |  | 
| 769 |         handleBaseNodeEnd(node: tspan); | 
| 770 |     } | 
| 771 |  | 
| 772 |     if (preserveWhiteSpace && (needsRichText || styleResolver->currentFillGradient() != nullptr)) | 
| 773 |         text = QStringLiteral("<span style=\"white-space: pre-wrap\">" ) + text + QStringLiteral("</span>" ); | 
| 774 |  | 
| 775 |     QFont font = styleResolver->painter().font(); | 
| 776 |     if (font.pixelSize() <= 0 && font.pointSize() > 0) | 
| 777 |         font.setPixelSize(font.pointSize()); // Pixel size stored as point size by SVG parser | 
| 778 |  | 
| 779 |     font.setHintingPreference(QFont::PreferNoHinting); | 
| 780 |  | 
| 781 | #if QT_CONFIG(texthtmlparser) | 
| 782 |     if (needsPathNode) { | 
| 783 |         QTextDocument document; | 
| 784 |         document.setHtml(text); | 
| 785 |         if (isTextArea && node->size().width() > 0) | 
| 786 |             document.setTextWidth(node->size().width()); | 
| 787 |         document.setDefaultFont(font); | 
| 788 |         document.pageCount(); // Force layout | 
| 789 |  | 
| 790 |         QTextBlock block = document.firstBlock(); | 
| 791 |         while (block.isValid()) { | 
| 792 |             QTextLayout *lout = block.layout(); | 
| 793 |  | 
| 794 |             if (lout != nullptr) { | 
| 795 |                 QRectF boundingRect = lout->boundingRect(); | 
| 796 |  | 
| 797 |                 // If this block has requested the current SVG font, we override it | 
| 798 |                 // (note that this limits the text to one svg font, but this is also the case | 
| 799 |                 // in the QPainter at the moment, and needs a more centralized solution in Qt Svg | 
| 800 |                 // first) | 
| 801 |                 QFont blockFont = block.charFormat().font(); | 
| 802 |                 if (svgFont != nullptr | 
| 803 |                     && blockFont.family() == svgFont->m_familyName) { | 
| 804 |                     QRawFont rawFont; | 
| 805 |                     QRawFontPrivate *rawFontD = QRawFontPrivate::get(font: rawFont); | 
| 806 |                     rawFontD->setFontEngine(fontEngine->cloneWithSize(blockFont.pixelSize())); | 
| 807 |  | 
| 808 |                     lout->setRawFont(rawFont); | 
| 809 |                 } | 
| 810 |  | 
| 811 |                 auto addPathForFormat = [&](QPainterPath p, QTextCharFormat fmt) { | 
| 812 |                     PathNodeInfo info; | 
| 813 |                     fillCommonNodeInfo(node, info); | 
| 814 |                     auto fillStyle = node->style().fill; | 
| 815 |                     if (fillStyle) | 
| 816 |                         info.fillRule = fillStyle->fillRule(); | 
| 817 |  | 
| 818 |                     if (fmt.hasProperty(propertyId: QTextCharFormat::ForegroundBrush)) { | 
| 819 |                         info.fillColor = fmt.foreground().color(); | 
| 820 |                         if (fmt.foreground().gradient() != nullptr && fmt.foreground().gradient()->type() != QGradient::NoGradient) | 
| 821 |                             info.grad = *fmt.foreground().gradient(); | 
| 822 |                     } else { | 
| 823 |                         info.fillColor = styleResolver->currentFillColor(); | 
| 824 |                     } | 
| 825 |  | 
| 826 |                     info.painterPath = p; | 
| 827 |  | 
| 828 |                     const QGradient *strokeGradient = styleResolver->currentStrokeGradient(); | 
| 829 |                     QPen pen; | 
| 830 |                     if (fmt.hasProperty(propertyId: QTextCharFormat::TextOutline)) { | 
| 831 |                         pen = fmt.textOutline(); | 
| 832 |                         if (strokeGradient == nullptr) { | 
| 833 |                             info.strokeStyle = StrokeStyle::fromPen(p: pen); | 
| 834 |                             info.strokeStyle.color = pen.color(); | 
| 835 |                         } | 
| 836 |                     } else { | 
| 837 |                         pen = styleResolver->currentStroke(); | 
| 838 |                         if (strokeGradient == nullptr) { | 
| 839 |                             info.strokeStyle = StrokeStyle::fromPen(p: pen); | 
| 840 |                             info.strokeStyle.color = styleResolver->currentStrokeColor(); | 
| 841 |                         } | 
| 842 |                     } | 
| 843 |  | 
| 844 |                     if (info.grad.type() == QGradient::NoGradient && styleResolver->currentFillGradient() != nullptr) | 
| 845 |                         info.grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity()); | 
| 846 |  | 
| 847 |                     info.fillTransform = styleResolver->currentFillTransform(); | 
| 848 |  | 
| 849 |                     m_generator->generatePath(info, overrideBoundingRect: boundingRect); | 
| 850 |  | 
| 851 |                     if (strokeGradient != nullptr) { | 
| 852 |                         PathNodeInfo strokeInfo; | 
| 853 |                         fillCommonNodeInfo(node, info&: strokeInfo); | 
| 854 |  | 
| 855 |                         strokeInfo.grad = *strokeGradient; | 
| 856 |  | 
| 857 |                         QPainterPathStroker stroker(pen); | 
| 858 |                         strokeInfo.painterPath = stroker.createStroke(path: p); | 
| 859 |                         m_generator->generatePath(info: strokeInfo, overrideBoundingRect: boundingRect); | 
| 860 |                     } | 
| 861 |                 }; | 
| 862 |  | 
| 863 |                 qreal baselineOffset = -QFontMetricsF(font).ascent(); | 
| 864 |                 if (lout->lineCount() > 0 && lout->lineAt(i: 0).isValid()) | 
| 865 |                     baselineOffset = -lout->lineAt(i: 0).ascent(); | 
| 866 |  | 
| 867 |                 const QPointF baselineTranslation(0.0, baselineOffset); | 
| 868 |                 auto glyphsToPath = [&](QList<QGlyphRun> glyphRuns, qreal width) { | 
| 869 |                     QList<QPainterPath> paths; | 
| 870 |                     for (const QGlyphRun &glyphRun : glyphRuns) { | 
| 871 |                         QRawFont font = glyphRun.rawFont(); | 
| 872 |                         QList<quint32> glyphIndexes = glyphRun.glyphIndexes(); | 
| 873 |                         QList<QPointF> positions = glyphRun.positions(); | 
| 874 |  | 
| 875 |                         for (qsizetype j = 0; j < glyphIndexes.size(); ++j) { | 
| 876 |                             quint32 glyphIndex = glyphIndexes.at(i: j); | 
| 877 |                             const QPointF &pos = positions.at(i: j); | 
| 878 |  | 
| 879 |                             QPainterPath p = font.pathForGlyph(glyphIndex); | 
| 880 |                             p.translate(offset: pos + node->position() + baselineTranslation); | 
| 881 |                             if (styleResolver->states().textAnchor == Qt::AlignHCenter) | 
| 882 |                                 p.translate(offset: QPointF(-0.5 * width, 0)); | 
| 883 |                             else if (styleResolver->states().textAnchor == Qt::AlignRight) | 
| 884 |                                 p.translate(offset: QPointF(-width, 0)); | 
| 885 |                             paths.append(t: p); | 
| 886 |                         } | 
| 887 |                     } | 
| 888 |  | 
| 889 |                     return paths; | 
| 890 |                 }; | 
| 891 |  | 
| 892 |                 QList<QTextLayout::FormatRange> formats = block.textFormats(); | 
| 893 |                 for (int i = 0; i < formats.size(); ++i) { | 
| 894 |                     QTextLayout::FormatRange range = formats.at(i); | 
| 895 |  | 
| 896 |                     // If we hit a "multi" anchor, it means we have additional formats to apply | 
| 897 |                     // for both this and the subsequent range, so we merge them. | 
| 898 |                     if (!range.format.anchorNames().isEmpty() | 
| 899 |                         && range.format.anchorNames().first().startsWith(QStringLiteral("multi" )) | 
| 900 |                         && i < formats.size() - 1) { | 
| 901 |                         QTextLayout::FormatRange  = formats.at(i: ++i); | 
| 902 |                         range.length += nextRange.length; | 
| 903 |                         range.format.merge(other: nextRange.format); | 
| 904 |                     } | 
| 905 |                     QList<QGlyphRun> glyphRuns = lout->glyphRuns(from: range.start, length: range.length); | 
| 906 |                     QList<QPainterPath> paths = glyphsToPath(glyphRuns, lout->minimumWidth()); | 
| 907 |                     for (const QPainterPath &path : paths) | 
| 908 |                         addPathForFormat(path, range.format); | 
| 909 |                 } | 
| 910 |             } | 
| 911 |  | 
| 912 |             block = block.next(); | 
| 913 |         } | 
| 914 |     } else | 
| 915 | #endif | 
| 916 |     { | 
| 917 |         TextNodeInfo info; | 
| 918 |         fillCommonNodeInfo(node, info); | 
| 919 |  | 
| 920 |         info.position = node->position(); | 
| 921 |         info.size = node->size(); | 
| 922 |         info.font = font; | 
| 923 |         info.text = text; | 
| 924 |         info.isTextArea = isTextArea; | 
| 925 |         info.needsRichText = needsRichText; | 
| 926 |         info.fillColor = styleResolver->currentFillColor(); | 
| 927 |         info.alignment = styleResolver->states().textAnchor; | 
| 928 |         info.strokeColor = styleResolver->currentStrokeColor(); | 
| 929 |  | 
| 930 |         m_generator->generateTextNode(info); | 
| 931 |     } | 
| 932 |  | 
| 933 |     handleBaseNodeEnd(node); | 
| 934 |  | 
| 935 |     if (fontEngine != nullptr) { | 
| 936 |         fontEngine->ref.deref(); | 
| 937 |         Q_ASSERT(fontEngine->ref.loadRelaxed() == 0); | 
| 938 |         delete fontEngine; | 
| 939 |     } | 
| 940 | } | 
| 941 |  | 
| 942 | void QSvgVisitorImpl::visitUseNode(const QSvgUse *node) | 
| 943 | { | 
| 944 |     QSvgNode *link = node->link(); | 
| 945 |     if (!link) | 
| 946 |         return; | 
| 947 |  | 
| 948 |     handleBaseNodeSetup(node); | 
| 949 |     UseNodeInfo info; | 
| 950 |     fillCommonNodeInfo(node, info); | 
| 951 |  | 
| 952 |     info.stage = StructureNodeStage::Start; | 
| 953 |     info.startPos = node->start(); | 
| 954 |  | 
| 955 |     m_generator->generateUseNode(info); | 
| 956 |  | 
| 957 |     QSvgVisitor::traverse(node: link); | 
| 958 |  | 
| 959 |     info.stage = StructureNodeStage::End; | 
| 960 |     m_generator->generateUseNode(info); | 
| 961 |     handleBaseNodeEnd(node); | 
| 962 | } | 
| 963 |  | 
| 964 | bool QSvgVisitorImpl::visitSwitchNodeStart(const QSvgSwitch *node) | 
| 965 | { | 
| 966 |     QSvgNode *link = node->childToRender(); | 
| 967 |     if (!link) | 
| 968 |         return false; | 
| 969 |  | 
| 970 |     QSvgVisitor::traverse(node: link); | 
| 971 |  | 
| 972 |     return false; | 
| 973 | } | 
| 974 |  | 
| 975 | void QSvgVisitorImpl::visitSwitchNodeEnd(const QSvgSwitch *node) | 
| 976 | { | 
| 977 |     Q_UNUSED(node); | 
| 978 | } | 
| 979 |  | 
| 980 | bool QSvgVisitorImpl::visitDefsNodeStart(const QSvgDefs *node) | 
| 981 | { | 
| 982 |     Q_UNUSED(node) | 
| 983 |  | 
| 984 |     return m_generator->generateDefsNode(info: NodeInfo{}); | 
| 985 | } | 
| 986 |  | 
| 987 | bool QSvgVisitorImpl::visitStructureNodeStart(const QSvgStructureNode *node) | 
| 988 | { | 
| 989 |     constexpr bool forceSeparatePaths = false; | 
| 990 |     handleBaseNodeSetup(node); | 
| 991 |  | 
| 992 |     StructureNodeInfo info; | 
| 993 |  | 
| 994 |     fillCommonNodeInfo(node, info); | 
| 995 |     info.forceSeparatePaths = forceSeparatePaths; | 
| 996 |     info.isPathContainer = isPathContainer(node); | 
| 997 |     info.stage = StructureNodeStage::Start; | 
| 998 |  | 
| 999 |     return m_generator->generateStructureNode(info); | 
| 1000 | } | 
| 1001 |  | 
| 1002 | void QSvgVisitorImpl::visitStructureNodeEnd(const QSvgStructureNode *node) | 
| 1003 | { | 
| 1004 |     handleBaseNodeEnd(node); | 
| 1005 |     //    qCDebug(lcQuickVectorGraphics) << "REVERT" << node->nodeId() << node->type() << (m_styleResolver->painter().pen().style() != Qt::NoPen) << m_styleResolver->painter().pen().color().name() | 
| 1006 |     //             << (m_styleResolver->painter().pen().brush().style() != Qt::NoBrush) << m_styleResolver->painter().pen().brush().color().name(); | 
| 1007 |  | 
| 1008 |     StructureNodeInfo info; | 
| 1009 |     fillCommonNodeInfo(node, info); | 
| 1010 |     info.stage = StructureNodeStage::End; | 
| 1011 |  | 
| 1012 |     m_generator->generateStructureNode(info); | 
| 1013 | } | 
| 1014 |  | 
| 1015 | bool QSvgVisitorImpl::visitDocumentNodeStart(const QSvgTinyDocument *node) | 
| 1016 | { | 
| 1017 |     handleBaseNodeSetup(node); | 
| 1018 |  | 
| 1019 |     StructureNodeInfo info; | 
| 1020 |     fillCommonNodeInfo(node, info); | 
| 1021 |  | 
| 1022 |     const QSvgTinyDocument *doc = static_cast<const QSvgTinyDocument *>(node); | 
| 1023 |     info.size = doc->size(); | 
| 1024 |     info.viewBox = doc->viewBox(); | 
| 1025 |     info.isPathContainer = isPathContainer(node); | 
| 1026 |     info.forceSeparatePaths = false; | 
| 1027 |     info.stage = StructureNodeStage::Start; | 
| 1028 |  | 
| 1029 |     return m_generator->generateRootNode(info); | 
| 1030 | } | 
| 1031 |  | 
| 1032 | void QSvgVisitorImpl::visitDocumentNodeEnd(const QSvgTinyDocument *node) | 
| 1033 | { | 
| 1034 |     handleBaseNodeEnd(node); | 
| 1035 |     qCDebug(lcQuickVectorImage) << "REVERT"  << node->nodeId() << node->type() << (styleResolver->painter().pen().style() != Qt::NoPen) | 
| 1036 |                                    << styleResolver->painter().pen().color().name() << (styleResolver->painter().pen().brush().style() != Qt::NoBrush) | 
| 1037 |                                    << styleResolver->painter().pen().brush().color().name(); | 
| 1038 |  | 
| 1039 |     StructureNodeInfo info; | 
| 1040 |     fillCommonNodeInfo(node, info); | 
| 1041 |     info.stage = StructureNodeStage::End; | 
| 1042 |  | 
| 1043 |     m_generator->generateRootNode(info); | 
| 1044 | } | 
| 1045 |  | 
| 1046 | void QSvgVisitorImpl::fillCommonNodeInfo(const QSvgNode *node, NodeInfo &info) | 
| 1047 | { | 
| 1048 |     info.nodeId = node->nodeId(); | 
| 1049 |     info.typeName = node->typeName(); | 
| 1050 |     info.isDefaultTransform = node->style().transform.isDefault(); | 
| 1051 |     info.transform = !info.isDefaultTransform ? node->style().transform->qtransform() : QTransform(); | 
| 1052 |     info.isDefaultOpacity = node->style().opacity.isDefault(); | 
| 1053 |     info.opacity = !info.isDefaultOpacity ? node->style().opacity->opacity() : 1.0; | 
| 1054 |     info.isVisible = node->isVisible(); | 
| 1055 |     info.isDisplayed = node->displayMode() != QSvgNode::DisplayMode::NoneMode; | 
| 1056 | } | 
| 1057 |  | 
| 1058 | void QSvgVisitorImpl::handleBaseNodeSetup(const QSvgNode *node) | 
| 1059 | { | 
| 1060 |     qCDebug(lcQuickVectorImage) << "Before SETUP"  << node << "fill"  << styleResolver->currentFillColor() | 
| 1061 |                                    << "stroke"  << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth() | 
| 1062 |                                    << node->nodeId() << " type: "  << node->typeName()  << " "  << node->type(); | 
| 1063 |  | 
| 1064 |     node->applyStyle(p: &styleResolver->painter(), states&: styleResolver->states()); | 
| 1065 |  | 
| 1066 |     qCDebug(lcQuickVectorImage) << "After SETUP"  << node << "fill"  << styleResolver->currentFillColor() | 
| 1067 |                                    << "stroke"  << styleResolver->currentStrokeColor() | 
| 1068 |                                    << styleResolver->currentStrokeWidth() << node->nodeId(); | 
| 1069 | } | 
| 1070 |  | 
| 1071 | void QSvgVisitorImpl::handleBaseNode(const QSvgNode *node) | 
| 1072 | { | 
| 1073 |     NodeInfo info; | 
| 1074 |     fillCommonNodeInfo(node, info); | 
| 1075 |  | 
| 1076 |     m_generator->generateNodeBase(info); | 
| 1077 | } | 
| 1078 |  | 
| 1079 | void QSvgVisitorImpl::handleBaseNodeEnd(const QSvgNode *node) | 
| 1080 | { | 
| 1081 |     node->revertStyle(p: &styleResolver->painter(), states&: styleResolver->states()); | 
| 1082 |  | 
| 1083 |     qCDebug(lcQuickVectorImage) << "After END"  << node << "fill"  << styleResolver->currentFillColor() | 
| 1084 |                                    << "stroke"  << styleResolver->currentStrokeColor() << styleResolver->currentStrokeWidth() | 
| 1085 |                                    << node->nodeId(); | 
| 1086 | } | 
| 1087 |  | 
| 1088 | void QSvgVisitorImpl::handlePathNode(const QSvgNode *node, const QPainterPath &path) | 
| 1089 | { | 
| 1090 |     handleBaseNodeSetup(node); | 
| 1091 |  | 
| 1092 |     PathNodeInfo info; | 
| 1093 |     fillCommonNodeInfo(node, info); | 
| 1094 |     auto fillStyle = node->style().fill; | 
| 1095 |     if (fillStyle) | 
| 1096 |         info.fillRule = fillStyle->fillRule(); | 
| 1097 |  | 
| 1098 |     const QGradient *strokeGradient = styleResolver->currentStrokeGradient(); | 
| 1099 |  | 
| 1100 |     info.painterPath = path; | 
| 1101 |     info.fillColor = styleResolver->currentFillColor(); | 
| 1102 |     if (strokeGradient == nullptr) { | 
| 1103 |         info.strokeStyle = StrokeStyle::fromPen(p: styleResolver->currentStroke()); | 
| 1104 |         info.strokeStyle.color = styleResolver->currentStrokeColor(); | 
| 1105 |     } | 
| 1106 |     if (styleResolver->currentFillGradient() != nullptr) | 
| 1107 |         info.grad = styleResolver->applyOpacityToGradient(gradient: *styleResolver->currentFillGradient(), opacity: styleResolver->currentFillOpacity()); | 
| 1108 |     info.fillTransform = styleResolver->currentFillTransform(); | 
| 1109 |  | 
| 1110 |     m_generator->generatePath(info); | 
| 1111 |  | 
| 1112 |     if (strokeGradient != nullptr) { | 
| 1113 |         PathNodeInfo strokeInfo; | 
| 1114 |         fillCommonNodeInfo(node, info&: strokeInfo); | 
| 1115 |  | 
| 1116 |         strokeInfo.grad = *strokeGradient; | 
| 1117 |  | 
| 1118 |         QPainterPathStroker stroker(styleResolver->currentStroke()); | 
| 1119 |         strokeInfo.painterPath = stroker.createStroke(path); | 
| 1120 |         m_generator->generatePath(info: strokeInfo); | 
| 1121 |     } | 
| 1122 |  | 
| 1123 |     handleBaseNodeEnd(node); | 
| 1124 | } | 
| 1125 |  | 
| 1126 | QT_END_NAMESPACE | 
| 1127 |  |