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 }