File indexing completed on 2024-05-19 04:24:57
0001 /* This file is part of the KDE project 0002 * 0003 * SPDX-FileCopyrightText: 2006-2007 Thomas Zander <zander@kde.org> 0004 * SPDX-FileCopyrightText: 2006-2008 Jan Hambrecht <jaham@gmx.net> 0005 * SPDX-FileCopyrightText: 2007, 2009 Thorsten Zachmann <zachmann@kde.org> 0006 * SPDX-FileCopyrightText: 2012 Inge Wallin <inge@lysator.liu.se> 0007 * 0008 * SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 // Own 0012 #include "KoShapeStroke.h" 0013 0014 // Posix 0015 #include <math.h> 0016 0017 // Qt 0018 #include <QPainterPath> 0019 #include <QPainter> 0020 0021 // Calligra 0022 0023 // Flake 0024 #include "KoShape.h" 0025 #include "KoShapeSavingContext.h" 0026 #include "KoPathShape.h" 0027 #include "KoMarker.h" 0028 #include "KoInsets.h" 0029 #include <KoPathSegment.h> 0030 #include <KoPathPoint.h> 0031 #include <cmath> 0032 #include "KisQPainterStateSaver.h" 0033 0034 #include "kis_global.h" 0035 0036 class Q_DECL_HIDDEN KoShapeStroke::Private 0037 { 0038 public: 0039 Private(KoShapeStroke *_q) : q(_q) {} 0040 KoShapeStroke *q; 0041 0042 void paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const; 0043 void paintMarkers(const KoShape *shape, QPainter &painter, const QPen &pen) const; 0044 QColor color; 0045 QPen pen; 0046 QBrush brush; 0047 }; 0048 0049 namespace { 0050 QPair<qreal, qreal> anglesForSegment(KoPathSegment segment) { 0051 const qreal eps = 1e-6; 0052 0053 if (segment.degree() < 3) { 0054 segment = segment.toCubic(); 0055 } 0056 0057 QList<QPointF> points = segment.controlPoints(); 0058 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(points.size() == 4, qMakePair(0.0, 0.0)); 0059 QPointF vec1 = points[1] - points[0]; 0060 QPointF vec2 = points[3] - points[2]; 0061 0062 if (vec1.manhattanLength() < eps) { 0063 points[1] = segment.pointAt(eps); 0064 vec1 = points[1] - points[0]; 0065 } 0066 0067 if (vec2.manhattanLength() < eps) { 0068 points[2] = segment.pointAt(1.0 - eps); 0069 vec2 = points[3] - points[2]; 0070 } 0071 0072 const qreal angle1 = std::atan2(vec1.y(), vec1.x()); 0073 const qreal angle2 = std::atan2(vec2.y(), vec2.x()); 0074 return qMakePair(angle1, angle2); 0075 } 0076 } 0077 0078 void KoShapeStroke::Private::paintBorder(const KoShape *shape, QPainter &painter, const QPen &pen) const 0079 { 0080 if (!pen.isCosmetic() && pen.style() != Qt::NoPen) { 0081 const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape); 0082 if (pathShape) { 0083 QPainterPath path = pathShape->pathStroke(pen); 0084 0085 painter.fillPath(path, pen.brush()); 0086 0087 return; 0088 } 0089 0090 painter.strokePath(shape->outline(), pen); 0091 } 0092 } 0093 void KoShapeStroke::Private::paintMarkers(const KoShape *shape, QPainter &painter, const QPen &pen) const 0094 { 0095 if (!pen.isCosmetic() && pen.style() != Qt::NoPen) { 0096 const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape); 0097 if (pathShape) { 0098 0099 if (!pathShape->hasMarkers()) return; 0100 0101 const bool autoFillMarkers = pathShape->autoFillMarkers(); 0102 KoMarker *startMarker = pathShape->marker(KoFlake::StartMarker); 0103 KoMarker *midMarker = pathShape->marker(KoFlake::MidMarker); 0104 KoMarker *endMarker = pathShape->marker(KoFlake::EndMarker); 0105 0106 for (int i = 0; i < pathShape->subpathCount(); i++) { 0107 const int numSubPoints = pathShape->subpathPointCount(i); 0108 if (numSubPoints < 2) continue; 0109 0110 const bool isClosedSubpath = pathShape->isClosedSubpath(i); 0111 0112 qreal firstAngle = 0.0; 0113 { 0114 KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, 0)); 0115 firstAngle= anglesForSegment(segment).first; 0116 } 0117 0118 const int numSegments = isClosedSubpath ? numSubPoints : numSubPoints - 1; 0119 0120 qreal lastAngle = 0.0; 0121 { 0122 KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, numSegments - 1)); 0123 lastAngle = anglesForSegment(segment).second; 0124 } 0125 0126 qreal previousAngle = 0.0; 0127 0128 for (int j = 0; j < numSegments; j++) { 0129 KoPathSegment segment = pathShape->segmentByIndex(KoPathPointIndex(i, j)); 0130 QPair<qreal, qreal> angles = anglesForSegment(segment); 0131 0132 const qreal angle1 = angles.first; 0133 const qreal angle2 = angles.second; 0134 0135 if (j == 0 && startMarker) { 0136 const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : firstAngle; 0137 if (autoFillMarkers) { 0138 startMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle); 0139 } 0140 startMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle); 0141 } 0142 0143 if (j > 0 && midMarker) { 0144 const qreal angle = bisectorAngle(previousAngle, angle1); 0145 if (autoFillMarkers) { 0146 midMarker->applyShapeStroke(shape, q, segment.first()->point(), pen.widthF(), angle); 0147 } 0148 midMarker->paintAtPosition(&painter, segment.first()->point(), pen.widthF(), angle); 0149 } 0150 0151 if (j == numSegments - 1 && endMarker) { 0152 const qreal angle = isClosedSubpath ? bisectorAngle(firstAngle, lastAngle) : lastAngle; 0153 if (autoFillMarkers) { 0154 endMarker->applyShapeStroke(shape, q, segment.second()->point(), pen.widthF(), angle); 0155 } 0156 endMarker->paintAtPosition(&painter, segment.second()->point(), pen.widthF(), angle); 0157 } 0158 0159 previousAngle = angle2; 0160 } 0161 } 0162 } 0163 } 0164 } 0165 0166 KoShapeStroke::KoShapeStroke() 0167 : d(new Private(this)) 0168 { 0169 d->color = QColor(Qt::black); 0170 // we are not rendering stroke with zero width anymore 0171 // so lets use a default width of 1.0 0172 d->pen.setWidthF(1.0); 0173 } 0174 0175 KoShapeStroke::KoShapeStroke(const KoShapeStroke &other) 0176 : KoShapeStrokeModel(), d(new Private(this)) 0177 { 0178 d->color = other.d->color; 0179 d->pen = other.d->pen; 0180 d->brush = other.d->brush; 0181 } 0182 0183 KoShapeStroke::KoShapeStroke(qreal lineWidth, const QColor &color) 0184 : d(new Private(this)) 0185 { 0186 d->pen.setWidthF(qMax(qreal(0.0), lineWidth)); 0187 d->pen.setJoinStyle(Qt::MiterJoin); 0188 d->color = color; 0189 } 0190 0191 KoShapeStroke::~KoShapeStroke() 0192 { 0193 delete d; 0194 } 0195 0196 KoShapeStroke &KoShapeStroke::operator = (const KoShapeStroke &rhs) 0197 { 0198 if (this == &rhs) 0199 return *this; 0200 0201 d->pen = rhs.d->pen; 0202 d->color = rhs.d->color; 0203 d->brush = rhs.d->brush; 0204 0205 return *this; 0206 } 0207 0208 void KoShapeStroke::strokeInsets(const KoShape *shape, KoInsets &insets) const 0209 { 0210 Q_UNUSED(shape); 0211 0212 // '0.5' --- since we draw a line half inside, and half outside the object. 0213 qreal extent = 0.5 * (d->pen.widthF() >= 0 ? d->pen.widthF() : 1.0); 0214 0215 // if we have square cap, we need a little more space 0216 // -> sqrt((0.5*penWidth)^2 + (0.5*penWidth)^2) 0217 if (capStyle() == Qt::SquareCap) { 0218 extent *= M_SQRT2; 0219 } 0220 0221 if (joinStyle() == Qt::MiterJoin) { 0222 // miter limit in Qt is normalized by the line width (and not half-width) 0223 extent = qMax(extent, d->pen.widthF() * miterLimit()); 0224 } 0225 0226 insets.top = extent; 0227 insets.bottom = extent; 0228 insets.left = extent; 0229 insets.right = extent; 0230 } 0231 0232 qreal KoShapeStroke::strokeMaxMarkersInset(const KoShape *shape) const 0233 { 0234 qreal result = 0.0; 0235 0236 const KoPathShape *pathShape = dynamic_cast<const KoPathShape *>(shape); 0237 if (pathShape && pathShape->hasMarkers()) { 0238 const qreal lineWidth = d->pen.widthF(); 0239 0240 QVector<const KoMarker*> markers; 0241 markers << pathShape->marker(KoFlake::StartMarker); 0242 markers << pathShape->marker(KoFlake::MidMarker); 0243 markers << pathShape->marker(KoFlake::EndMarker); 0244 0245 Q_FOREACH (const KoMarker *marker, markers) { 0246 if (marker) { 0247 result = qMax(result, marker->maxInset(lineWidth)); 0248 } 0249 } 0250 } 0251 0252 return result; 0253 } 0254 0255 bool KoShapeStroke::hasTransparency() const 0256 { 0257 return d->color.alpha() > 0; 0258 } 0259 0260 QPen KoShapeStroke::resultLinePen() const 0261 { 0262 QPen pen = d->pen; 0263 0264 if (d->brush.gradient()) { 0265 pen.setBrush(d->brush); 0266 } else { 0267 pen.setColor(d->color.isValid() ? d->color : Qt::transparent); 0268 } 0269 0270 return pen; 0271 } 0272 0273 void KoShapeStroke::paint(const KoShape *shape, QPainter &painter) const 0274 { 0275 KisQPainterStateSaver saver(&painter); 0276 0277 d->paintBorder(shape, painter, resultLinePen()); 0278 } 0279 0280 void KoShapeStroke::paintMarkers(const KoShape *shape, QPainter &painter) const 0281 { 0282 KisQPainterStateSaver saver(&painter); 0283 0284 d->paintMarkers(shape, painter, resultLinePen()); 0285 } 0286 0287 bool KoShapeStroke::compareFillTo(const KoShapeStrokeModel *other) 0288 { 0289 if (!other) return false; 0290 0291 const KoShapeStroke *stroke = dynamic_cast<const KoShapeStroke*>(other); 0292 if (!stroke) return false; 0293 0294 return (d->brush.gradient() && d->brush == stroke->d->brush) || 0295 (!d->brush.gradient() && d->color == stroke->d->color); 0296 } 0297 0298 bool KoShapeStroke::compareStyleTo(const KoShapeStrokeModel *other) 0299 { 0300 if (!other) return false; 0301 0302 const KoShapeStroke *stroke = dynamic_cast<const KoShapeStroke*>(other); 0303 if (!stroke) return false; 0304 0305 QPen pen1 = d->pen; 0306 QPen pen2 = stroke->d->pen; 0307 0308 // just a random color top avoid comparison of that property 0309 pen1.setColor(Qt::magenta); 0310 pen2.setColor(Qt::magenta); 0311 0312 return pen1 == pen2; 0313 } 0314 0315 bool KoShapeStroke::isVisible() const 0316 { 0317 return d->pen.widthF() > 0 && 0318 (d->brush.gradient() || d->color.alpha() > 0); 0319 } 0320 0321 void KoShapeStroke::setCapStyle(Qt::PenCapStyle style) 0322 { 0323 d->pen.setCapStyle(style); 0324 } 0325 0326 Qt::PenCapStyle KoShapeStroke::capStyle() const 0327 { 0328 return d->pen.capStyle(); 0329 } 0330 0331 void KoShapeStroke::setJoinStyle(Qt::PenJoinStyle style) 0332 { 0333 d->pen.setJoinStyle(style); 0334 } 0335 0336 Qt::PenJoinStyle KoShapeStroke::joinStyle() const 0337 { 0338 return d->pen.joinStyle(); 0339 } 0340 0341 void KoShapeStroke::setLineWidth(qreal lineWidth) 0342 { 0343 d->pen.setWidthF(qMax(qreal(0.0), lineWidth)); 0344 } 0345 0346 qreal KoShapeStroke::lineWidth() const 0347 { 0348 return d->pen.widthF(); 0349 } 0350 0351 void KoShapeStroke::setMiterLimit(qreal miterLimit) 0352 { 0353 d->pen.setMiterLimit(miterLimit); 0354 } 0355 0356 qreal KoShapeStroke::miterLimit() const 0357 { 0358 return d->pen.miterLimit(); 0359 } 0360 0361 QColor KoShapeStroke::color() const 0362 { 0363 return d->color; 0364 } 0365 0366 void KoShapeStroke::setColor(const QColor &color) 0367 { 0368 d->color = color; 0369 } 0370 0371 void KoShapeStroke::setLineStyle(Qt::PenStyle style, const QVector<qreal> &dashes) 0372 { 0373 if (style < Qt::CustomDashLine) { 0374 d->pen.setStyle(style); 0375 } else { 0376 d->pen.setDashPattern(dashes); 0377 } 0378 } 0379 0380 Qt::PenStyle KoShapeStroke::lineStyle() const 0381 { 0382 return d->pen.style(); 0383 } 0384 0385 QVector<qreal> KoShapeStroke::lineDashes() const 0386 { 0387 return d->pen.dashPattern(); 0388 } 0389 0390 void KoShapeStroke::setDashOffset(qreal dashOffset) 0391 { 0392 d->pen.setDashOffset(dashOffset); 0393 } 0394 0395 qreal KoShapeStroke::dashOffset() const 0396 { 0397 return d->pen.dashOffset(); 0398 } 0399 0400 void KoShapeStroke::setLineBrush(const QBrush &brush) 0401 { 0402 d->brush = brush; 0403 } 0404 0405 QBrush KoShapeStroke::lineBrush() const 0406 { 0407 return d->brush; 0408 }