| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include "qsvgnode_p.h" |
| 5 | #include "qsvgtinydocument_p.h" |
| 6 | |
| 7 | #include <QLoggingCategory> |
| 8 | #include<QElapsedTimer> |
| 9 | #include <QtGui/qimageiohandler.h> |
| 10 | |
| 11 | #include "qdebug.h" |
| 12 | #include "qstack.h" |
| 13 | |
| 14 | #include <QtGui/private/qoutlinemapper_p.h> |
| 15 | |
| 16 | QT_BEGIN_NAMESPACE |
| 17 | |
| 18 | Q_DECLARE_LOGGING_CATEGORY(lcSvgDraw); |
| 19 | |
| 20 | Q_LOGGING_CATEGORY(lcSvgTiming, "qt.svg.timing" ) |
| 21 | |
| 22 | #if !defined(QT_SVG_SIZE_LIMIT) |
| 23 | # define QT_SVG_SIZE_LIMIT QT_RASTER_COORD_LIMIT |
| 24 | #endif |
| 25 | |
| 26 | QSvgNode::QSvgNode(QSvgNode *parent) |
| 27 | : m_parent(parent), |
| 28 | m_visible(true), |
| 29 | m_displayMode(BlockMode) |
| 30 | { |
| 31 | } |
| 32 | |
| 33 | QSvgNode::~QSvgNode() |
| 34 | { |
| 35 | |
| 36 | } |
| 37 | |
| 38 | void QSvgNode::(QPainter *p, QSvgExtraStates &states) |
| 39 | { |
| 40 | #ifndef QT_NO_DEBUG |
| 41 | QElapsedTimer qtSvgTimer; qtSvgTimer.start(); |
| 42 | #endif |
| 43 | |
| 44 | if (shouldDrawNode(p, states)) { |
| 45 | applyStyle(p, states); |
| 46 | QSvgNode *maskNode = this->hasMask() ? document()->namedNode(id: this->maskId()) : nullptr; |
| 47 | QSvgFilterContainer *filterNode = this->hasFilter() ? static_cast<QSvgFilterContainer*>(document()->namedNode(id: this->filterId())) |
| 48 | : nullptr; |
| 49 | if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported()) { |
| 50 | QTransform xf = p->transform(); |
| 51 | p->resetTransform(); |
| 52 | QRectF localRect = internalBounds(p, states); |
| 53 | p->setTransform(transform: xf); |
| 54 | QRectF boundsRect = xf.mapRect(filterNode->filterRegion(itemBounds: localRect)); |
| 55 | QImage proxy = drawIntoBuffer(p, states, boundsRect: boundsRect.toRect()); |
| 56 | proxy = filterNode->applyFilter(buffer: proxy, p, bounds: localRect); |
| 57 | if (maskNode && maskNode->type() == QSvgNode::Mask) { |
| 58 | boundsRect = QRectF(proxy.offset(), proxy.size()); |
| 59 | localRect = p->transform().inverted().mapRect(boundsRect); |
| 60 | QImage mask = static_cast<QSvgMask*>(maskNode)->createMask(p, states, localRect, globalRect: &boundsRect); |
| 61 | applyMaskToBuffer(proxy: &proxy, mask); |
| 62 | } |
| 63 | applyBufferToCanvas(p, proxy); |
| 64 | |
| 65 | } else if (maskNode && maskNode->type() == QSvgNode::Mask) { |
| 66 | QRectF boundsRect; |
| 67 | QImage mask = static_cast<QSvgMask*>(maskNode)->createMask(p, states, targetNode: this, globalRect: &boundsRect); |
| 68 | drawWithMask(p, states, mask, boundsRect: boundsRect.toRect()); |
| 69 | } else if (!qFuzzyCompare(p1: p->opacity(), p2: 1.0) && requiresGroupRendering()) { |
| 70 | QTransform xf = p->transform(); |
| 71 | p->resetTransform(); |
| 72 | |
| 73 | QRectF localRect = decoratedInternalBounds(p, states); |
| 74 | // adding safety border needed because of the antialiazing effects |
| 75 | QRectF boundsRect = xf.mapRect(localRect); |
| 76 | const int deltaX = boundsRect.width() * 0.1; |
| 77 | const int deltaY = boundsRect.height() * 0.1; |
| 78 | boundsRect = boundsRect.adjusted(xp1: -deltaX, yp1: -deltaY, xp2: deltaX, yp2: deltaY); |
| 79 | |
| 80 | p->setTransform(transform: xf); |
| 81 | |
| 82 | QImage proxy = drawIntoBuffer(p, states, boundsRect: boundsRect.toAlignedRect()); |
| 83 | applyBufferToCanvas(p, proxy); |
| 84 | } else { |
| 85 | if (separateFillStroke()) |
| 86 | fillThenStroke(p, states); |
| 87 | else |
| 88 | drawCommand(p, states); |
| 89 | |
| 90 | } |
| 91 | revertStyle(p, states); |
| 92 | } |
| 93 | |
| 94 | #ifndef QT_NO_DEBUG |
| 95 | if (Q_UNLIKELY(lcSvgTiming().isDebugEnabled())) |
| 96 | qCDebug(lcSvgTiming) << "Drawing" << typeName() << "took" << (qtSvgTimer.nsecsElapsed() / 1000000.0f) << "ms" ; |
| 97 | #endif |
| 98 | } |
| 99 | |
| 100 | void QSvgNode::(QPainter *p, QSvgExtraStates &states) |
| 101 | { |
| 102 | qreal oldOpacity = p->opacity(); |
| 103 | if (p->brush().style() != Qt::NoBrush) { |
| 104 | QPen oldPen = p->pen(); |
| 105 | p->setPen(Qt::NoPen); |
| 106 | p->setOpacity(oldOpacity * states.fillOpacity); |
| 107 | |
| 108 | drawCommand(p, states); |
| 109 | |
| 110 | p->setPen(oldPen); |
| 111 | } |
| 112 | if (p->pen() != Qt::NoPen && p->pen().brush() != Qt::NoBrush && p->pen().widthF() != 0) { |
| 113 | QBrush oldBrush = p->brush(); |
| 114 | p->setOpacity(oldOpacity * states.strokeOpacity); |
| 115 | p->setBrush(Qt::NoBrush); |
| 116 | |
| 117 | drawCommand(p, states); |
| 118 | |
| 119 | p->setBrush(oldBrush); |
| 120 | } |
| 121 | p->setOpacity(oldOpacity); |
| 122 | } |
| 123 | |
| 124 | void QSvgNode::(QPainter *p, QSvgExtraStates &states, const QImage &mask, const QRect &boundsRect) |
| 125 | { |
| 126 | QImage proxy = drawIntoBuffer(p, states, boundsRect); |
| 127 | if (proxy.isNull()) |
| 128 | return; |
| 129 | applyMaskToBuffer(proxy: &proxy, mask); |
| 130 | |
| 131 | p->save(); |
| 132 | p->resetTransform(); |
| 133 | p->drawImage(r: boundsRect, image: proxy); |
| 134 | p->restore(); |
| 135 | } |
| 136 | |
| 137 | QImage QSvgNode::(QPainter *p, QSvgExtraStates &states, const QRect &boundsRect) |
| 138 | { |
| 139 | QImage proxy; |
| 140 | if (!QImageIOHandler::allocateImage(size: boundsRect.size(), format: QImage::Format_ARGB32_Premultiplied, image: &proxy)) { |
| 141 | qCWarning(lcSvgDraw) << "The requested buffer size is too big, ignoring" ; |
| 142 | return proxy; |
| 143 | } |
| 144 | proxy.setOffset(boundsRect.topLeft()); |
| 145 | proxy.fill(color: Qt::transparent); |
| 146 | QPainter proxyPainter(&proxy); |
| 147 | proxyPainter.setPen(p->pen()); |
| 148 | proxyPainter.setBrush(p->brush()); |
| 149 | proxyPainter.setFont(p->font()); |
| 150 | proxyPainter.translate(offset: -boundsRect.topLeft()); |
| 151 | proxyPainter.setTransform(transform: p->transform(), combine: true); |
| 152 | proxyPainter.setRenderHints(hints: p->renderHints()); |
| 153 | if (separateFillStroke()) |
| 154 | fillThenStroke(p: &proxyPainter, states); |
| 155 | else |
| 156 | drawCommand(p: &proxyPainter, states); |
| 157 | return proxy; |
| 158 | } |
| 159 | |
| 160 | void QSvgNode::applyMaskToBuffer(QImage *proxy, QImage mask) const |
| 161 | { |
| 162 | QPainter proxyPainter(proxy); |
| 163 | proxyPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut); |
| 164 | proxyPainter.resetTransform(); |
| 165 | proxyPainter.drawImage(r: QRect(0, 0, mask.width(), mask.height()), image: mask); |
| 166 | } |
| 167 | |
| 168 | void QSvgNode::applyBufferToCanvas(QPainter *p, QImage proxy) const |
| 169 | { |
| 170 | QTransform xf = p->transform(); |
| 171 | p->resetTransform(); |
| 172 | p->drawImage(r: QRect(proxy.offset(), proxy.size()), image: proxy); |
| 173 | p->setTransform(transform: xf); |
| 174 | } |
| 175 | |
| 176 | bool QSvgNode::isDescendantOf(const QSvgNode *parent) const |
| 177 | { |
| 178 | const QSvgNode *n = this; |
| 179 | while (n) { |
| 180 | if (n == parent) |
| 181 | return true; |
| 182 | n = n->m_parent; |
| 183 | } |
| 184 | return false; |
| 185 | } |
| 186 | |
| 187 | void QSvgNode::appendStyleProperty(QSvgStyleProperty *prop, const QString &id) |
| 188 | { |
| 189 | //qDebug()<<"appending "<<prop->type()<< " ("<< id <<") "<<"to "<<this<<this->type(); |
| 190 | QSvgTinyDocument *doc; |
| 191 | switch (prop->type()) { |
| 192 | case QSvgStyleProperty::QUALITY: |
| 193 | m_style.quality = static_cast<QSvgQualityStyle*>(prop); |
| 194 | break; |
| 195 | case QSvgStyleProperty::FILL: |
| 196 | m_style.fill = static_cast<QSvgFillStyle*>(prop); |
| 197 | break; |
| 198 | case QSvgStyleProperty::VIEWPORT_FILL: |
| 199 | m_style.viewportFill = static_cast<QSvgViewportFillStyle*>(prop); |
| 200 | break; |
| 201 | case QSvgStyleProperty::FONT: |
| 202 | m_style.font = static_cast<QSvgFontStyle*>(prop); |
| 203 | break; |
| 204 | case QSvgStyleProperty::STROKE: |
| 205 | m_style.stroke = static_cast<QSvgStrokeStyle*>(prop); |
| 206 | break; |
| 207 | case QSvgStyleProperty::SOLID_COLOR: |
| 208 | m_style.solidColor = static_cast<QSvgSolidColorStyle*>(prop); |
| 209 | doc = document(); |
| 210 | if (doc && !id.isEmpty()) |
| 211 | doc->addNamedStyle(id, style: m_style.solidColor); |
| 212 | break; |
| 213 | case QSvgStyleProperty::GRADIENT: |
| 214 | m_style.gradient = static_cast<QSvgGradientStyle*>(prop); |
| 215 | doc = document(); |
| 216 | if (doc && !id.isEmpty()) |
| 217 | doc->addNamedStyle(id, style: m_style.gradient); |
| 218 | break; |
| 219 | case QSvgStyleProperty::PATTERN: |
| 220 | m_style.pattern = static_cast<QSvgPatternStyle*>(prop); |
| 221 | doc = document(); |
| 222 | if (doc && !id.isEmpty()) |
| 223 | doc->addNamedStyle(id, style: m_style.pattern); |
| 224 | break; |
| 225 | case QSvgStyleProperty::TRANSFORM: |
| 226 | m_style.transform = static_cast<QSvgTransformStyle*>(prop); |
| 227 | break; |
| 228 | case QSvgStyleProperty::ANIMATE_COLOR: |
| 229 | m_style.animateColors.append( |
| 230 | t: static_cast<QSvgAnimateColor*>(prop)); |
| 231 | break; |
| 232 | case QSvgStyleProperty::ANIMATE_TRANSFORM: |
| 233 | m_style.animateTransforms.append( |
| 234 | t: static_cast<QSvgAnimateTransform*>(prop)); |
| 235 | break; |
| 236 | case QSvgStyleProperty::OPACITY: |
| 237 | m_style.opacity = static_cast<QSvgOpacityStyle*>(prop); |
| 238 | break; |
| 239 | case QSvgStyleProperty::COMP_OP: |
| 240 | m_style.compop = static_cast<QSvgCompOpStyle*>(prop); |
| 241 | break; |
| 242 | default: |
| 243 | qDebug(msg: "QSvgNode: Trying to append unknown property!" ); |
| 244 | break; |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | void QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 249 | { |
| 250 | m_style.apply(p, node: this, states); |
| 251 | } |
| 252 | |
| 253 | /*! |
| 254 | \internal |
| 255 | |
| 256 | Apply the styles of all parents to the painter and the states. |
| 257 | The styles are applied from the top level node to the current node. |
| 258 | This function can be used to set the correct style for a node |
| 259 | if it's draw function is triggered out of the ordinary draw context, |
| 260 | for example the mask node, that is cross-referenced. |
| 261 | */ |
| 262 | void QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 263 | { |
| 264 | if (parent()) |
| 265 | parent()->applyStyleRecursive(p, states); |
| 266 | applyStyle(p, states); |
| 267 | } |
| 268 | |
| 269 | void QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 270 | { |
| 271 | m_style.revert(p, states); |
| 272 | } |
| 273 | |
| 274 | void QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 275 | { |
| 276 | revertStyle(p, states); |
| 277 | if (parent()) |
| 278 | parent()->revertStyleRecursive(p, states); |
| 279 | } |
| 280 | |
| 281 | QSvgStyleProperty * QSvgNode::styleProperty(QSvgStyleProperty::Type type) const |
| 282 | { |
| 283 | const QSvgNode *node = this; |
| 284 | while (node) { |
| 285 | switch (type) { |
| 286 | case QSvgStyleProperty::QUALITY: |
| 287 | if (node->m_style.quality) |
| 288 | return node->m_style.quality; |
| 289 | break; |
| 290 | case QSvgStyleProperty::FILL: |
| 291 | if (node->m_style.fill) |
| 292 | return node->m_style.fill; |
| 293 | break; |
| 294 | case QSvgStyleProperty::VIEWPORT_FILL: |
| 295 | if (m_style.viewportFill) |
| 296 | return node->m_style.viewportFill; |
| 297 | break; |
| 298 | case QSvgStyleProperty::FONT: |
| 299 | if (node->m_style.font) |
| 300 | return node->m_style.font; |
| 301 | break; |
| 302 | case QSvgStyleProperty::STROKE: |
| 303 | if (node->m_style.stroke) |
| 304 | return node->m_style.stroke; |
| 305 | break; |
| 306 | case QSvgStyleProperty::SOLID_COLOR: |
| 307 | if (node->m_style.solidColor) |
| 308 | return node->m_style.solidColor; |
| 309 | break; |
| 310 | case QSvgStyleProperty::GRADIENT: |
| 311 | if (node->m_style.gradient) |
| 312 | return node->m_style.gradient; |
| 313 | break; |
| 314 | case QSvgStyleProperty::PATTERN: |
| 315 | if (node->m_style.pattern) |
| 316 | return node->m_style.pattern; |
| 317 | break; |
| 318 | case QSvgStyleProperty::TRANSFORM: |
| 319 | if (node->m_style.transform) |
| 320 | return node->m_style.transform; |
| 321 | break; |
| 322 | case QSvgStyleProperty::ANIMATE_COLOR: |
| 323 | if (!node->m_style.animateColors.isEmpty()) |
| 324 | return node->m_style.animateColors.first(); |
| 325 | break; |
| 326 | case QSvgStyleProperty::ANIMATE_TRANSFORM: |
| 327 | if (!node->m_style.animateTransforms.isEmpty()) |
| 328 | return node->m_style.animateTransforms.first(); |
| 329 | break; |
| 330 | case QSvgStyleProperty::OPACITY: |
| 331 | if (node->m_style.opacity) |
| 332 | return node->m_style.opacity; |
| 333 | break; |
| 334 | case QSvgStyleProperty::COMP_OP: |
| 335 | if (node->m_style.compop) |
| 336 | return node->m_style.compop; |
| 337 | break; |
| 338 | default: |
| 339 | break; |
| 340 | } |
| 341 | node = node->parent(); |
| 342 | } |
| 343 | |
| 344 | return 0; |
| 345 | } |
| 346 | |
| 347 | QSvgPaintStyleProperty * QSvgNode::styleProperty(const QString &id) const |
| 348 | { |
| 349 | QString rid = id; |
| 350 | if (rid.startsWith(c: QLatin1Char('#'))) |
| 351 | rid.remove(i: 0, len: 1); |
| 352 | QSvgTinyDocument *doc = document(); |
| 353 | return doc ? doc->namedStyle(id: rid) : 0; |
| 354 | } |
| 355 | |
| 356 | QRectF QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 357 | { |
| 358 | return internalBounds(p, states); |
| 359 | } |
| 360 | |
| 361 | QRectF QSvgNode::(QPainter *, QSvgExtraStates &) const |
| 362 | { |
| 363 | return QRectF(0, 0, 0, 0); |
| 364 | } |
| 365 | |
| 366 | QRectF QSvgNode::bounds() const |
| 367 | { |
| 368 | if (!m_cachedBounds.isEmpty()) |
| 369 | return m_cachedBounds; |
| 370 | |
| 371 | QImage dummy(1, 1, QImage::Format_RGB32); |
| 372 | QPainter p(&dummy); |
| 373 | initPainter(p: &p); |
| 374 | QSvgExtraStates states; |
| 375 | |
| 376 | if (parent()) |
| 377 | parent()->applyStyleRecursive(p: &p, states); |
| 378 | p.setWorldTransform(matrix: QTransform()); |
| 379 | m_cachedBounds = bounds(p: &p, states); |
| 380 | if (parent()) // always revert the style to not store old transformations |
| 381 | parent()->revertStyleRecursive(p: &p, states); |
| 382 | return m_cachedBounds; |
| 383 | } |
| 384 | |
| 385 | QSvgTinyDocument * QSvgNode::document() const |
| 386 | { |
| 387 | QSvgTinyDocument *doc = nullptr; |
| 388 | QSvgNode *node = const_cast<QSvgNode*>(this); |
| 389 | while (node && node->type() != QSvgNode::Doc) { |
| 390 | node = node->parent(); |
| 391 | } |
| 392 | doc = static_cast<QSvgTinyDocument*>(node); |
| 393 | |
| 394 | return doc; |
| 395 | } |
| 396 | |
| 397 | QString QSvgNode::typeName() const |
| 398 | { |
| 399 | switch (type()) { |
| 400 | case Doc: return QStringLiteral("svg" ); |
| 401 | case Group: return QStringLiteral("g" ); |
| 402 | case Defs: return QStringLiteral("defs" ); |
| 403 | case Switch: return QStringLiteral("switch" ); |
| 404 | case Animation: return QStringLiteral("animation" ); |
| 405 | case Circle: return QStringLiteral("circle" ); |
| 406 | case Ellipse: return QStringLiteral("ellipse" ); |
| 407 | case Image: return QStringLiteral("image" ); |
| 408 | case Line: return QStringLiteral("line" ); |
| 409 | case Path: return QStringLiteral("path" ); |
| 410 | case Polygon: return QStringLiteral("polygon" ); |
| 411 | case Polyline: return QStringLiteral("polyline" ); |
| 412 | case Rect: return QStringLiteral("rect" ); |
| 413 | case Text: return QStringLiteral("text" ); |
| 414 | case Textarea: return QStringLiteral("textarea" ); |
| 415 | case Tspan: return QStringLiteral("tspan" ); |
| 416 | case Use: return QStringLiteral("use" ); |
| 417 | case Video: return QStringLiteral("video" ); |
| 418 | case Mask: return QStringLiteral("mask" ); |
| 419 | case Symbol: return QStringLiteral("symbol" ); |
| 420 | case Marker: return QStringLiteral("marker" ); |
| 421 | case Pattern: return QStringLiteral("pattern" ); |
| 422 | case Filter: return QStringLiteral("filter" ); |
| 423 | case FeMerge: return QStringLiteral("feMerge" ); |
| 424 | case FeMergenode: return QStringLiteral("feMergeNode" ); |
| 425 | case FeColormatrix: return QStringLiteral("feColorMatrix" ); |
| 426 | case FeGaussianblur: return QStringLiteral("feGaussianBlur" ); |
| 427 | case FeOffset: return QStringLiteral("feOffset" ); |
| 428 | case FeComposite: return QStringLiteral("feComposite" ); |
| 429 | case FeFlood: return QStringLiteral("feFlood" ); |
| 430 | case FeUnsupported: return QStringLiteral("feUnsupported" ); |
| 431 | } |
| 432 | return QStringLiteral("unknown" ); |
| 433 | } |
| 434 | |
| 435 | void QSvgNode::setRequiredFeatures(const QStringList &lst) |
| 436 | { |
| 437 | m_requiredFeatures = lst; |
| 438 | } |
| 439 | |
| 440 | const QStringList & QSvgNode::requiredFeatures() const |
| 441 | { |
| 442 | return m_requiredFeatures; |
| 443 | } |
| 444 | |
| 445 | void QSvgNode::setRequiredExtensions(const QStringList &lst) |
| 446 | { |
| 447 | m_requiredExtensions = lst; |
| 448 | } |
| 449 | |
| 450 | const QStringList & QSvgNode::requiredExtensions() const |
| 451 | { |
| 452 | return m_requiredExtensions; |
| 453 | } |
| 454 | |
| 455 | void QSvgNode::setRequiredLanguages(const QStringList &lst) |
| 456 | { |
| 457 | m_requiredLanguages = lst; |
| 458 | } |
| 459 | |
| 460 | const QStringList & QSvgNode::requiredLanguages() const |
| 461 | { |
| 462 | return m_requiredLanguages; |
| 463 | } |
| 464 | |
| 465 | void QSvgNode::setRequiredFormats(const QStringList &lst) |
| 466 | { |
| 467 | m_requiredFormats = lst; |
| 468 | } |
| 469 | |
| 470 | const QStringList & QSvgNode::requiredFormats() const |
| 471 | { |
| 472 | return m_requiredFormats; |
| 473 | } |
| 474 | |
| 475 | void QSvgNode::setRequiredFonts(const QStringList &lst) |
| 476 | { |
| 477 | m_requiredFonts = lst; |
| 478 | } |
| 479 | |
| 480 | const QStringList & QSvgNode::requiredFonts() const |
| 481 | { |
| 482 | return m_requiredFonts; |
| 483 | } |
| 484 | |
| 485 | void QSvgNode::setVisible(bool visible) |
| 486 | { |
| 487 | //propagate visibility change of true to the parent |
| 488 | //not propagating false is just a small performance |
| 489 | //degradation since we'll iterate over children without |
| 490 | //drawing any of them |
| 491 | if (m_parent && visible && !m_parent->isVisible()) |
| 492 | m_parent->setVisible(true); |
| 493 | |
| 494 | m_visible = visible; |
| 495 | } |
| 496 | |
| 497 | QRectF QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 498 | { |
| 499 | applyStyle(p, states); |
| 500 | QRectF rect = internalBounds(p, states); |
| 501 | revertStyle(p, states); |
| 502 | return rect; |
| 503 | } |
| 504 | |
| 505 | QRectF QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 506 | { |
| 507 | return filterRegion(bounds: internalBounds(p, states)); |
| 508 | } |
| 509 | |
| 510 | QRectF QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 511 | { |
| 512 | applyStyle(p, states); |
| 513 | QRectF rect = decoratedInternalBounds(p, states); |
| 514 | revertStyle(p, states); |
| 515 | return rect; |
| 516 | } |
| 517 | |
| 518 | void QSvgNode::setNodeId(const QString &i) |
| 519 | { |
| 520 | m_id = i; |
| 521 | } |
| 522 | |
| 523 | void QSvgNode::setXmlClass(const QString &str) |
| 524 | { |
| 525 | m_class = str; |
| 526 | } |
| 527 | |
| 528 | QString QSvgNode::maskId() const |
| 529 | { |
| 530 | return m_maskId; |
| 531 | } |
| 532 | |
| 533 | void QSvgNode::setMaskId(const QString &str) |
| 534 | { |
| 535 | m_maskId = str; |
| 536 | } |
| 537 | |
| 538 | bool QSvgNode::hasMask() const |
| 539 | { |
| 540 | if (document()->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) |
| 541 | return false; |
| 542 | return !m_maskId.isEmpty(); |
| 543 | } |
| 544 | |
| 545 | QString QSvgNode::filterId() const |
| 546 | { |
| 547 | return m_filterId; |
| 548 | } |
| 549 | |
| 550 | void QSvgNode::setFilterId(const QString &str) |
| 551 | { |
| 552 | m_filterId = str; |
| 553 | } |
| 554 | |
| 555 | bool QSvgNode::hasFilter() const |
| 556 | { |
| 557 | if (document()->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) |
| 558 | return false; |
| 559 | return !m_filterId.isEmpty(); |
| 560 | } |
| 561 | |
| 562 | QString QSvgNode::markerStartId() const |
| 563 | { |
| 564 | return m_markerStartId; |
| 565 | } |
| 566 | |
| 567 | void QSvgNode::setMarkerStartId(const QString &str) |
| 568 | { |
| 569 | m_markerStartId = str; |
| 570 | } |
| 571 | |
| 572 | bool QSvgNode::hasMarkerStart() const |
| 573 | { |
| 574 | if (document()->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) |
| 575 | return false; |
| 576 | return !m_markerStartId.isEmpty(); |
| 577 | } |
| 578 | |
| 579 | QString QSvgNode::markerMidId() const |
| 580 | { |
| 581 | return m_markerMidId; |
| 582 | } |
| 583 | |
| 584 | void QSvgNode::setMarkerMidId(const QString &str) |
| 585 | { |
| 586 | m_markerMidId = str; |
| 587 | } |
| 588 | |
| 589 | bool QSvgNode::hasMarkerMid() const |
| 590 | { |
| 591 | if (document()->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) |
| 592 | return false; |
| 593 | return !m_markerMidId.isEmpty(); |
| 594 | } |
| 595 | |
| 596 | QString QSvgNode::markerEndId() const |
| 597 | { |
| 598 | return m_markerEndId; |
| 599 | } |
| 600 | |
| 601 | void QSvgNode::setMarkerEndId(const QString &str) |
| 602 | { |
| 603 | m_markerEndId = str; |
| 604 | } |
| 605 | |
| 606 | bool QSvgNode::hasMarkerEnd() const |
| 607 | { |
| 608 | if (document()->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) |
| 609 | return false; |
| 610 | return !m_markerEndId.isEmpty(); |
| 611 | } |
| 612 | |
| 613 | bool QSvgNode::hasAnyMarker() const |
| 614 | { |
| 615 | if (document()->options().testFlag(flag: QtSvg::Tiny12FeaturesOnly)) |
| 616 | return false; |
| 617 | return hasMarkerStart() || hasMarkerMid() || hasMarkerEnd(); |
| 618 | } |
| 619 | |
| 620 | bool QSvgNode::requiresGroupRendering() const |
| 621 | { |
| 622 | return false; |
| 623 | } |
| 624 | |
| 625 | void QSvgNode::setDisplayMode(DisplayMode mode) |
| 626 | { |
| 627 | m_displayMode = mode; |
| 628 | } |
| 629 | |
| 630 | QSvgNode::DisplayMode QSvgNode::displayMode() const |
| 631 | { |
| 632 | return m_displayMode; |
| 633 | } |
| 634 | |
| 635 | qreal QSvgNode::strokeWidth(QPainter *p) |
| 636 | { |
| 637 | const QPen &pen = p->pen(); |
| 638 | if (pen.style() == Qt::NoPen || pen.brush().style() == Qt::NoBrush || pen.isCosmetic()) |
| 639 | return 0; |
| 640 | return pen.widthF(); |
| 641 | } |
| 642 | |
| 643 | void QSvgNode::initPainter(QPainter *p) |
| 644 | { |
| 645 | QPen pen(Qt::NoBrush, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin); |
| 646 | pen.setMiterLimit(4); |
| 647 | p->setPen(pen); |
| 648 | p->setBrush(Qt::black); |
| 649 | p->setRenderHint(hint: QPainter::Antialiasing); |
| 650 | p->setRenderHint(hint: QPainter::SmoothPixmapTransform); |
| 651 | QFont font(p->font()); |
| 652 | if (font.pointSize() < 0 && font.pixelSize() > 0) { |
| 653 | font.setPointSizeF(font.pixelSize() * 72.0 / p->device()->logicalDpiY()); |
| 654 | p->setFont(font); |
| 655 | } |
| 656 | } |
| 657 | |
| 658 | QRectF QSvgNode::boundsOnStroke(QPainter *p, const QPainterPath &path, |
| 659 | qreal width, BoundsMode mode) |
| 660 | { |
| 661 | QPainterPathStroker stroker; |
| 662 | stroker.setWidth(width); |
| 663 | if (mode == BoundsMode::IncludeMiterLimit) { |
| 664 | stroker.setJoinStyle(p->pen().joinStyle()); |
| 665 | stroker.setMiterLimit(p->pen().miterLimit()); |
| 666 | } |
| 667 | QPainterPath stroke = stroker.createStroke(path); |
| 668 | return p->transform().map(p: stroke).boundingRect(); |
| 669 | } |
| 670 | |
| 671 | bool QSvgNode::(QPainter *p, QSvgExtraStates &states) const |
| 672 | { |
| 673 | if (m_displayMode == DisplayMode::NoneMode) |
| 674 | return false; |
| 675 | |
| 676 | if (document() && document()->options().testFlag(flag: QtSvg::AssumeTrustedSource)) |
| 677 | return true; |
| 678 | |
| 679 | QRectF brect = internalFastBounds(p, states); |
| 680 | if (brect.width() <= QT_SVG_SIZE_LIMIT && brect.height() <= QT_SVG_SIZE_LIMIT) { |
| 681 | return true; |
| 682 | } else { |
| 683 | qCWarning(lcSvgDraw) << "Shape of type" << type() << "ignored because it will take too long to rasterize (bounding rect=" << brect << ")." |
| 684 | << "Enable AssumeTrustedSource in QSvgHandler or set QT_SVG_DEFAULT_OPTIONS=2 to disable this check." ; |
| 685 | return false; |
| 686 | } |
| 687 | } |
| 688 | |
| 689 | QRectF QSvgNode::filterRegion(QRectF bounds) const |
| 690 | { |
| 691 | QSvgFilterContainer *filterNode = hasFilter() |
| 692 | ? static_cast<QSvgFilterContainer*>(document()->namedNode(id: filterId())) |
| 693 | : nullptr; |
| 694 | |
| 695 | if (filterNode && filterNode->type() == QSvgNode::Filter && filterNode->supported()) |
| 696 | return filterNode->filterRegion(itemBounds: bounds); |
| 697 | |
| 698 | return bounds; |
| 699 | } |
| 700 | |
| 701 | QT_END_NAMESPACE |
| 702 | |