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 }