File indexing completed on 2024-05-12 15:56:42

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2011 Thorsten Zachmann <zachmann@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "KoMarker.h"
0008 
0009 #include <KoXmlNS.h>
0010 #include "KoPathShape.h"
0011 #include "KoPathShapeLoader.h"
0012 #include "KoShapeLoadingContext.h"
0013 #include "KoShapeSavingContext.h"
0014 #include "KoShapePainter.h"
0015 #include <KoShapeStroke.h>
0016 #include <KoGradientBackground.h>
0017 #include <KoColorBackground.h>
0018 
0019 
0020 #include <QString>
0021 #include <QUrl>
0022 #include <QPainterPath>
0023 #include <QPainter>
0024 
0025 #include "kis_global.h"
0026 #include "kis_algebra_2d.h"
0027 
0028 class Q_DECL_HIDDEN KoMarker::Private
0029 {
0030 public:
0031     Private()
0032         : coordinateSystem(StrokeWidth),
0033           referenceSize(3,3),
0034           hasAutoOrientation(false),
0035           explicitOrientation(0)
0036     {}
0037 
0038     ~Private() {
0039         // shape manager that is stored in the painter should be destroyed
0040         // before the shapes themselves
0041         shapePainter.reset();
0042         qDeleteAll(shapes);
0043     }
0044 
0045     bool operator==(const KoMarker::Private &other) const
0046     {
0047         // WARNING: comparison of shapes is extremely fuzzy! Don't
0048         //          trust it in life-critical cases!
0049 
0050         return name == other.name &&
0051             coordinateSystem == other.coordinateSystem &&
0052             referencePoint == other.referencePoint &&
0053             referenceSize == other.referenceSize &&
0054             hasAutoOrientation == other.hasAutoOrientation &&
0055             explicitOrientation == other.explicitOrientation &&
0056             compareShapesTo(other.shapes);
0057     }
0058 
0059     Private(const Private &rhs)
0060         : name(rhs.name),
0061           coordinateSystem(rhs.coordinateSystem),
0062           referencePoint(rhs.referencePoint),
0063           referenceSize(rhs.referenceSize),
0064           hasAutoOrientation(rhs.hasAutoOrientation),
0065           explicitOrientation(rhs.explicitOrientation)
0066     {
0067         Q_FOREACH (KoShape *shape, rhs.shapes) {
0068             shapes << shape->cloneShape();
0069         }
0070     }
0071 
0072     QString name;
0073     MarkerCoordinateSystem coordinateSystem;
0074     QPointF referencePoint;
0075     QSizeF referenceSize;
0076 
0077     bool hasAutoOrientation;
0078     qreal explicitOrientation;
0079 
0080     QList<KoShape*> shapes;
0081     QScopedPointer<KoShapePainter> shapePainter;
0082 
0083     bool compareShapesTo(const QList<KoShape*> other) const {
0084         if (shapes.size() != other.size()) return false;
0085 
0086         for (int i = 0; i < shapes.size(); i++) {
0087             if (shapes[i]->outline() != other[i]->outline() ||
0088                 shapes[i]->absoluteTransformation() != other[i]->absoluteTransformation()) {
0089 
0090                 return false;
0091             }
0092         }
0093 
0094         return true;
0095     }
0096 
0097     QTransform markerTransform(qreal strokeWidth, qreal nodeAngle, const QPointF &pos = QPointF()) {
0098         const QTransform translate = QTransform::fromTranslate(-referencePoint.x(), -referencePoint.y());
0099 
0100         QTransform t = translate;
0101 
0102         if (coordinateSystem == StrokeWidth) {
0103             t *= QTransform::fromScale(strokeWidth, strokeWidth);
0104         }
0105 
0106         const qreal angle = hasAutoOrientation ? nodeAngle : explicitOrientation;
0107         if (angle != 0.0) {
0108             QTransform r;
0109             r.rotateRadians(angle);
0110             t *= r;
0111         }
0112 
0113         t *= QTransform::fromTranslate(pos.x(), pos.y());
0114 
0115         return t;
0116     }
0117 };
0118 
0119 KoMarker::KoMarker()
0120 : d(new Private())
0121 {
0122 }
0123 
0124 KoMarker::~KoMarker()
0125 {
0126     delete d;
0127 }
0128 
0129 QString KoMarker::name() const
0130 {
0131     return d->name;
0132 }
0133 
0134 KoMarker::KoMarker(const KoMarker &rhs)
0135     : QSharedData(rhs),
0136       d(new Private(*rhs.d))
0137 {
0138 }
0139 
0140 bool KoMarker::operator==(const KoMarker &other) const
0141 {
0142     return *d == *other.d;
0143 }
0144 
0145 void KoMarker::setCoordinateSystem(KoMarker::MarkerCoordinateSystem value)
0146 {
0147     d->coordinateSystem = value;
0148 }
0149 
0150 KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystem() const
0151 {
0152     return d->coordinateSystem;
0153 }
0154 
0155 KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystemFromString(const QString &value)
0156 {
0157     MarkerCoordinateSystem result = StrokeWidth;
0158 
0159     if (value == "userSpaceOnUse") {
0160         result = UserSpaceOnUse;
0161     }
0162 
0163     return result;
0164 }
0165 
0166 QString KoMarker::coordinateSystemToString(KoMarker::MarkerCoordinateSystem value)
0167 {
0168     return
0169         value == StrokeWidth ?
0170         "strokeWidth" :
0171                 "userSpaceOnUse";
0172 }
0173 
0174 void KoMarker::setReferencePoint(const QPointF &value)
0175 {
0176     d->referencePoint = value;
0177 }
0178 
0179 QPointF KoMarker::referencePoint() const
0180 {
0181     return d->referencePoint;
0182 }
0183 
0184 void KoMarker::setReferenceSize(const QSizeF &size)
0185 {
0186     d->referenceSize = size;
0187 }
0188 
0189 QSizeF KoMarker::referenceSize() const
0190 {
0191     return d->referenceSize;
0192 }
0193 
0194 bool KoMarker::hasAutoOtientation() const
0195 {
0196     return d->hasAutoOrientation;
0197 }
0198 
0199 void KoMarker::setAutoOrientation(bool value)
0200 {
0201     d->hasAutoOrientation = value;
0202 }
0203 
0204 qreal KoMarker::explicitOrientation() const
0205 {
0206     return d->explicitOrientation;
0207 }
0208 
0209 void KoMarker::setExplicitOrientation(qreal value)
0210 {
0211     d->explicitOrientation = value;
0212 }
0213 
0214 void KoMarker::setShapes(const QList<KoShape *> &shapes)
0215 {
0216     d->shapes = shapes;
0217 
0218     if (d->shapePainter) {
0219         d->shapePainter->setShapes(shapes);
0220     }
0221 }
0222 
0223 QList<KoShape *> KoMarker::shapes() const
0224 {
0225     return d->shapes;
0226 }
0227 
0228 void KoMarker::paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
0229 {
0230     QTransform oldTransform = painter->transform();
0231 
0232     if (!d->shapePainter) {
0233         d->shapePainter.reset(new KoShapePainter());
0234         d->shapePainter->setShapes(d->shapes);
0235     }
0236 
0237     painter->setTransform(d->markerTransform(strokeWidth, nodeAngle, pos), true);
0238     d->shapePainter->paint(*painter);
0239 
0240     painter->setTransform(oldTransform);
0241 }
0242 
0243 qreal KoMarker::maxInset(qreal strokeWidth) const
0244 {
0245     QRectF shapesBounds = boundingRect(strokeWidth, 0.0); // normalized to 0,0
0246     qreal result = 0.0;
0247 
0248     result = qMax(KisAlgebra2D::norm(shapesBounds.topLeft()), result);
0249     result = qMax(KisAlgebra2D::norm(shapesBounds.topRight()), result);
0250     result = qMax(KisAlgebra2D::norm(shapesBounds.bottomLeft()), result);
0251     result = qMax(KisAlgebra2D::norm(shapesBounds.bottomRight()), result);
0252 
0253     return result;
0254 }
0255 
0256 QRectF KoMarker::boundingRect(qreal strokeWidth, qreal nodeAngle) const
0257 {
0258     QRectF shapesBounds = KoShape::boundingRect(d->shapes);
0259 
0260     const QTransform t = d->markerTransform(strokeWidth, nodeAngle);
0261 
0262     if (!t.isIdentity()) {
0263         shapesBounds = t.mapRect(shapesBounds);
0264     }
0265 
0266     return shapesBounds;
0267 }
0268 
0269 QPainterPath KoMarker::outline(qreal strokeWidth, qreal nodeAngle) const
0270 {
0271     QPainterPath outline;
0272     Q_FOREACH (KoShape *shape, d->shapes) {
0273         outline |= shape->absoluteTransformation().map(shape->outline());
0274     }
0275 
0276     const QTransform t = d->markerTransform(strokeWidth, nodeAngle);
0277 
0278     if (!t.isIdentity()) {
0279         outline = t.map(outline);
0280     }
0281 
0282     return outline;
0283 }
0284 
0285 void KoMarker::drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position)
0286 {
0287     const QRectF outlineRect = outline(pen.widthF(), 0).boundingRect(); // normalized to 0,0
0288     QPointF marker;
0289     QPointF start;
0290     QPointF end;
0291 
0292     if (position == KoFlake::StartMarker) {
0293         marker = QPointF(-outlineRect.left() + previewRect.left(), previewRect.center().y());
0294         start = marker;
0295         end = QPointF(previewRect.right(), start.y());
0296     } else if (position == KoFlake::MidMarker) {
0297         start = QPointF(previewRect.left(), previewRect.center().y());
0298         marker = QPointF(-outlineRect.center().x() + previewRect.center().x(), start.y());
0299         end = QPointF(previewRect.right(), start.y());
0300     } else if (position == KoFlake::EndMarker) {
0301         start = QPointF(previewRect.left(), previewRect.center().y());
0302         marker = QPointF(-outlineRect.right() + previewRect.right(), start.y());
0303         end = marker;
0304     }
0305 
0306     painter->save();
0307     painter->setPen(pen);
0308     painter->setClipRect(previewRect);
0309 
0310     painter->drawLine(start, end);
0311     paintAtPosition(painter, marker, pen.widthF(), 0);
0312 
0313     painter->restore();
0314 }
0315 
0316 void KoMarker::applyShapeStroke(const KoShape *parentShape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle)
0317 {
0318     const QGradient *originalGradient = stroke->lineBrush().gradient();
0319 
0320     if (!originalGradient) {
0321         QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes);
0322         Q_FOREACH(KoShape *shape, linearizedShapes) {
0323             // update the stroke
0324             KoShapeStrokeSP shapeStroke = shape->stroke() ?
0325                         qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) :
0326                         KoShapeStrokeSP();
0327 
0328             if (shapeStroke) {
0329                 shapeStroke = toQShared(new KoShapeStroke(*shapeStroke));
0330 
0331                 shapeStroke->setLineBrush(QBrush());
0332                 shapeStroke->setColor(stroke->color());
0333 
0334                 shape->setStroke(shapeStroke);
0335             }
0336 
0337             // update the background
0338             if (shape->background()) {
0339                 QSharedPointer<KoColorBackground> bg(new KoColorBackground(stroke->color()));
0340                 shape->setBackground(bg);
0341             }
0342         }
0343     } else {
0344         QScopedPointer<QGradient> g(KoFlake::cloneGradient(originalGradient));
0345         KIS_ASSERT_RECOVER_RETURN(g);
0346 
0347         const QTransform markerTransformInverted =
0348                 d->markerTransform(strokeWidth, nodeAngle, pos).inverted();
0349 
0350         QTransform gradientToUser;
0351 
0352         // Unwrap the gradient to work in global mode
0353         if (g->coordinateMode() == QGradient::ObjectBoundingMode) {
0354             QRectF boundingRect =
0355                 parentShape ?
0356                 parentShape->outline().boundingRect() :
0357                 this->boundingRect(strokeWidth, nodeAngle);
0358 
0359             boundingRect = KisAlgebra2D::ensureRectNotSmaller(boundingRect, QSizeF(1.0, 1.0));
0360 
0361             gradientToUser = QTransform(boundingRect.width(), 0, 0, boundingRect.height(),
0362                                         boundingRect.x(), boundingRect.y());
0363 
0364             g->setCoordinateMode(QGradient::LogicalMode);
0365         }
0366 
0367         QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes);
0368         Q_FOREACH(KoShape *shape, linearizedShapes) {
0369             // shape-unwinding transform
0370             QTransform t = gradientToUser * markerTransformInverted * shape->absoluteTransformation().inverted();
0371 
0372             // update the stroke
0373             KoShapeStrokeSP shapeStroke = shape->stroke() ?
0374                         qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) :
0375                         KoShapeStrokeSP();
0376 
0377             if (shapeStroke) {
0378                 shapeStroke = toQShared(new KoShapeStroke(*shapeStroke));
0379 
0380                 QBrush brush(*g);
0381                 brush.setTransform(t);
0382                 shapeStroke->setLineBrush(brush);
0383                 shapeStroke->setColor(Qt::transparent);
0384                 shape->setStroke(shapeStroke);
0385             }
0386 
0387             // update the background
0388             if (shape->background()) {
0389 
0390                 QSharedPointer<KoGradientBackground> bg(new KoGradientBackground(KoFlake::cloneGradient(g.data()), t));
0391                 shape->setBackground(bg);
0392             }
0393         }
0394     }
0395 }