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 }