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