Warning, file /office/calligra/libs/flake/KoPathShape.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* This file is part of the KDE project 0002 Copyright (C) 2006-2008, 2010-2011 Thorsten Zachmann <zachmann@kde.org> 0003 Copyright (C) 2006-2011 Jan Hambrecht <jaham@gmx.net> 0004 Copyright (C) 2007-2009 Thomas Zander <zander@kde.org> 0005 Copyright (C) 2011 Jean-Nicolas Artaud <jeannicolasartaud@gmail.com> 0006 0007 This library is free software; you can redistribute it and/or 0008 modify it under the terms of the GNU Library General Public 0009 License as published by the Free Software Foundation; either 0010 version 2 of the License, or (at your option) any later version. 0011 0012 This library is distributed in the hope that it will be useful, 0013 but WITHOUT ANY WARRANTY without even the implied warranty of 0014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0015 Library General Public License for more details. 0016 0017 You should have received a copy of the GNU Library General Public License 0018 along with this library; see the file COPYING.LIB. If not, write to 0019 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0020 * Boston, MA 02110-1301, USA. 0021 */ 0022 0023 #include "KoPathShape.h" 0024 #include "KoPathShape_p.h" 0025 0026 #include "KoPathSegment.h" 0027 #include "KoOdfWorkaround.h" 0028 #include "KoPathPoint.h" 0029 #include "KoShapeStrokeModel.h" 0030 #include "KoViewConverter.h" 0031 #include "KoPathShapeLoader.h" 0032 #include "KoShapeSavingContext.h" 0033 #include "KoShapeLoadingContext.h" 0034 #include "KoShapeShadow.h" 0035 #include "KoShapeBackground.h" 0036 #include "KoShapeContainer.h" 0037 #include "KoFilterEffectStack.h" 0038 #include "KoMarker.h" 0039 #include "KoMarkerSharedLoadingData.h" 0040 #include "KoShapeStroke.h" 0041 #include "KoInsets.h" 0042 0043 #include <KoXmlReader.h> 0044 #include <KoXmlWriter.h> 0045 #include <KoXmlNS.h> 0046 #include <KoUnit.h> 0047 #include <KoGenStyle.h> 0048 #include <KoStyleStack.h> 0049 #include <KoOdfLoadingContext.h> 0050 0051 #include <FlakeDebug.h> 0052 #include <QPainter> 0053 #include <QPainterPath> 0054 0055 #include <qnumeric.h> // for qIsNaN 0056 static bool qIsNaNPoint(const QPointF &p) { 0057 return qIsNaN(p.x()) || qIsNaN(p.y()); 0058 } 0059 static const qreal DefaultMarkerWidth = 3.0; 0060 0061 KoPathShapePrivate::KoPathShapePrivate(KoPathShape *q) 0062 : KoTosContainerPrivate(q), 0063 fillRule(Qt::OddEvenFill), 0064 startMarker(KoMarkerData::MarkerStart), 0065 endMarker(KoMarkerData::MarkerEnd) 0066 { 0067 } 0068 0069 QRectF KoPathShapePrivate::handleRect(const QPointF &p, qreal radius) const 0070 { 0071 return QRectF(p.x() - radius, p.y() - radius, 2*radius, 2*radius); 0072 } 0073 0074 void KoPathShapePrivate::applyViewboxTransformation(const KoXmlElement &element) 0075 { 0076 // apply viewbox transformation 0077 const QRect viewBox = KoPathShape::loadOdfViewbox(element); 0078 if (! viewBox.isEmpty()) { 0079 // load the desired size 0080 QSizeF size; 0081 size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); 0082 size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); 0083 0084 // load the desired position 0085 QPointF pos; 0086 pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); 0087 pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); 0088 0089 // create matrix to transform original path data into desired size and position 0090 QTransform viewMatrix; 0091 viewMatrix.translate(-viewBox.left(), -viewBox.top()); 0092 viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); 0093 viewMatrix.translate(pos.x(), pos.y()); 0094 0095 // transform the path data 0096 map(viewMatrix); 0097 } 0098 } 0099 0100 KoPathShape::KoPathShape() 0101 :KoTosContainer(*(new KoPathShapePrivate(this))) 0102 { 0103 } 0104 0105 KoPathShape::KoPathShape(KoPathShapePrivate &dd) 0106 : KoTosContainer(dd) 0107 { 0108 } 0109 0110 KoPathShape::~KoPathShape() 0111 { 0112 clear(); 0113 } 0114 0115 void KoPathShape::saveContourOdf(KoShapeSavingContext &context, const QSizeF &scaleFactor) const 0116 { 0117 Q_D(const KoPathShape); 0118 0119 if (m_subpaths.length() <= 1) { 0120 QTransform matrix; 0121 matrix.scale(scaleFactor.width(), scaleFactor.height()); 0122 QString points; 0123 KoSubpath *subPath = m_subpaths.first(); 0124 KoSubpath::const_iterator pointIt(subPath->constBegin()); 0125 0126 KoPathPoint *currPoint= 0; 0127 // iterate over all points 0128 for (; pointIt != subPath->constEnd(); ++pointIt) { 0129 currPoint = *pointIt; 0130 0131 if (currPoint->activeControlPoint1() || currPoint->activeControlPoint2()) { 0132 break; 0133 } 0134 const QPointF p = matrix.map(currPoint->point()); 0135 points += QString("%1,%2 ").arg(qRound(1000*p.x())).arg(qRound(1000*p.y())); 0136 } 0137 0138 if (currPoint && !(currPoint->activeControlPoint1() || currPoint->activeControlPoint2())) { 0139 context.xmlWriter().startElement("draw:contour-polygon"); 0140 context.xmlWriter().addAttributePt("svg:width", size().width()); 0141 context.xmlWriter().addAttributePt("svg:height", size().height()); 0142 0143 const QSizeF s(size()); 0144 QString viewBox = QString("0 0 %1 %2").arg(qRound(1000*s.width())).arg(qRound(1000*s.height())); 0145 context.xmlWriter().addAttribute("svg:viewBox", viewBox); 0146 0147 context.xmlWriter().addAttribute("draw:points", points); 0148 0149 context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); 0150 context.xmlWriter().endElement(); 0151 0152 return; 0153 } 0154 } 0155 0156 // if we get here we couldn't save as polygon - let-s try contour-path 0157 context.xmlWriter().startElement("draw:contour-path"); 0158 saveOdfAttributes(context, OdfViewbox); 0159 0160 context.xmlWriter().addAttribute("svg:d", toString()); 0161 context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); 0162 context.xmlWriter().addAttribute("draw:recreate-on-edit", "true"); 0163 context.xmlWriter().endElement(); 0164 } 0165 0166 void KoPathShape::saveOdf(KoShapeSavingContext & context) const 0167 { 0168 Q_D(const KoPathShape); 0169 context.xmlWriter().startElement("draw:path"); 0170 saveOdfAttributes(context, OdfAllAttributes | OdfViewbox); 0171 0172 context.xmlWriter().addAttribute("svg:d", toString()); 0173 context.xmlWriter().addAttribute("calligra:nodeTypes", d->nodeTypes()); 0174 0175 saveOdfCommonChildElements(context); 0176 saveText(context); 0177 context.xmlWriter().endElement(); 0178 } 0179 0180 bool KoPathShape::loadContourOdf(const KoXmlElement &element, KoShapeLoadingContext &, const QSizeF &scaleFactor) 0181 { 0182 Q_D(KoPathShape); 0183 0184 // first clear the path data from the default path 0185 clear(); 0186 0187 if (element.localName() == "contour-polygon") { 0188 QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); 0189 points.replace(',', ' '); 0190 points.remove('\r'); 0191 points.remove('\n'); 0192 bool firstPoint = true; 0193 const QStringList coordinateList = points.split(' '); 0194 for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { 0195 QPointF point; 0196 point.setX((*it).toDouble()); 0197 ++it; 0198 point.setY((*it).toDouble()); 0199 if (firstPoint) { 0200 moveTo(point); 0201 firstPoint = false; 0202 } else 0203 lineTo(point); 0204 } 0205 close(); 0206 } else if (element.localName() == "contour-path") { 0207 KoPathShapeLoader loader(this); 0208 loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); 0209 d->loadNodeTypes(element); 0210 } 0211 0212 // apply viewbox transformation 0213 const QRect viewBox = KoPathShape::loadOdfViewbox(element); 0214 if (! viewBox.isEmpty()) { 0215 QSizeF size; 0216 size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString()))); 0217 size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString()))); 0218 0219 // create matrix to transform original path data into desired size and position 0220 QTransform viewMatrix; 0221 viewMatrix.translate(-viewBox.left(), -viewBox.top()); 0222 viewMatrix.scale(scaleFactor.width(), scaleFactor.height()); 0223 viewMatrix.scale(size.width() / viewBox.width(), size.height() / viewBox.height()); 0224 0225 // transform the path data 0226 d->map(viewMatrix); 0227 } 0228 setTransformation(QTransform()); 0229 0230 return true; 0231 } 0232 0233 bool KoPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context) 0234 { 0235 Q_D(KoPathShape); 0236 loadOdfAttributes(element, context, OdfMandatories | OdfAdditionalAttributes | OdfCommonChildElements); 0237 0238 // first clear the path data from the default path 0239 clear(); 0240 0241 if (element.localName() == "line") { 0242 QPointF start; 0243 start.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x1", ""))); 0244 start.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y1", ""))); 0245 QPointF end; 0246 end.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x2", ""))); 0247 end.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y2", ""))); 0248 moveTo(start); 0249 lineTo(end); 0250 } else if (element.localName() == "polyline" || element.localName() == "polygon") { 0251 QString points = element.attributeNS(KoXmlNS::draw, "points").simplified(); 0252 points.replace(',', ' '); 0253 points.remove('\r'); 0254 points.remove('\n'); 0255 bool firstPoint = true; 0256 const QStringList coordinateList = points.split(' '); 0257 for (QStringList::ConstIterator it = coordinateList.constBegin(); it != coordinateList.constEnd(); ++it) { 0258 QPointF point; 0259 point.setX((*it).toDouble()); 0260 ++it; 0261 point.setY((*it).toDouble()); 0262 if (firstPoint) { 0263 moveTo(point); 0264 firstPoint = false; 0265 } else 0266 lineTo(point); 0267 } 0268 if (element.localName() == "polygon") 0269 close(); 0270 } else { // path loading 0271 KoPathShapeLoader loader(this); 0272 loader.parseSvg(element.attributeNS(KoXmlNS::svg, "d"), true); 0273 d->loadNodeTypes(element); 0274 } 0275 0276 d->applyViewboxTransformation(element); 0277 QPointF pos = normalize(); 0278 setTransformation(QTransform()); 0279 0280 if (element.hasAttributeNS(KoXmlNS::svg, "x") || element.hasAttributeNS(KoXmlNS::svg, "y")) { 0281 pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString()))); 0282 pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString()))); 0283 } 0284 0285 setPosition(pos); 0286 0287 loadOdfAttributes(element, context, OdfTransformation); 0288 0289 // now that the correct transformation is set up 0290 // apply that matrix to the path geometry so that 0291 // we don't transform the stroke 0292 d->map(transformation()); 0293 setTransformation(QTransform()); 0294 normalize(); 0295 0296 loadText(element, context); 0297 0298 return true; 0299 } 0300 0301 QString KoPathShape::saveStyle(KoGenStyle &style, KoShapeSavingContext &context) const 0302 { 0303 Q_D(const KoPathShape); 0304 0305 style.addProperty("svg:fill-rule", d->fillRule == Qt::OddEvenFill ? "evenodd" : "nonzero"); 0306 0307 KoShapeStroke *lineBorder = dynamic_cast<KoShapeStroke*>(stroke()); 0308 qreal lineWidth = 0; 0309 if (lineBorder) { 0310 lineWidth = lineBorder->lineWidth(); 0311 } 0312 d->startMarker.saveStyle(style, lineWidth, context); 0313 d->endMarker.saveStyle(style, lineWidth, context); 0314 0315 return KoTosContainer::saveStyle(style, context); 0316 } 0317 0318 void KoPathShape::loadStyle(const KoXmlElement & element, KoShapeLoadingContext &context) 0319 { 0320 Q_D(KoPathShape); 0321 KoTosContainer::loadStyle(element, context); 0322 0323 KoStyleStack &styleStack = context.odfLoadingContext().styleStack(); 0324 styleStack.setTypeProperties("graphic"); 0325 0326 if (styleStack.hasProperty(KoXmlNS::svg, "fill-rule")) { 0327 QString rule = styleStack.property(KoXmlNS::svg, "fill-rule"); 0328 d->fillRule = (rule == "nonzero") ? Qt::WindingFill : Qt::OddEvenFill; 0329 } else { 0330 d->fillRule = Qt::WindingFill; 0331 #ifndef NWORKAROUND_ODF_BUGS 0332 KoOdfWorkaround::fixMissingFillRule(d->fillRule, context); 0333 #endif 0334 } 0335 0336 KoShapeStroke *lineBorder = dynamic_cast<KoShapeStroke*>(stroke()); 0337 qreal lineWidth = 0; 0338 if (lineBorder) { 0339 lineWidth = lineBorder->lineWidth(); 0340 } 0341 0342 d->startMarker.loadOdf(lineWidth, context); 0343 d->endMarker.loadOdf(lineWidth, context); 0344 } 0345 0346 QRect KoPathShape::loadOdfViewbox(const KoXmlElement & element) 0347 { 0348 QRect viewbox; 0349 0350 QString data = element.attributeNS(KoXmlNS::svg, QLatin1String("viewBox")); 0351 if (! data.isEmpty()) { 0352 data.replace(QLatin1Char(','), QLatin1Char(' ')); 0353 const QStringList coordinates = data.simplified().split(QLatin1Char(' '), QString::SkipEmptyParts); 0354 if (coordinates.count() == 4) { 0355 viewbox.setRect(coordinates.at(0).toInt(), coordinates.at(1).toInt(), 0356 coordinates.at(2).toInt(), coordinates.at(3).toInt()); 0357 } 0358 } 0359 0360 return viewbox; 0361 } 0362 0363 void KoPathShape::clear() 0364 { 0365 foreach(KoSubpath *subpath, m_subpaths) { 0366 foreach(KoPathPoint *point, *subpath) 0367 delete point; 0368 delete subpath; 0369 } 0370 m_subpaths.clear(); 0371 } 0372 0373 void KoPathShape::paint(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) 0374 { 0375 Q_D(KoPathShape); 0376 applyConversion(painter, converter); 0377 QPainterPath path(outline()); 0378 path.setFillRule(d->fillRule); 0379 0380 if (background()) { 0381 background()->paint(painter, converter, paintContext, path); 0382 } 0383 //d->paintDebug(painter); 0384 } 0385 0386 0387 #ifndef NDEBUG 0388 void KoPathShapePrivate::paintDebug(QPainter &painter) 0389 { 0390 Q_Q(KoPathShape); 0391 KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); 0392 int i = 0; 0393 0394 QPen pen(Qt::black, 0); 0395 painter.save(); 0396 painter.setPen(pen); 0397 for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { 0398 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0399 for (; it != (*pathIt)->constEnd(); ++it) { 0400 ++i; 0401 KoPathPoint *point = (*it); 0402 QRectF r(point->point(), QSizeF(5, 5)); 0403 r.translate(-2.5, -2.5); 0404 QPen pen(Qt::black, 0); 0405 painter.setPen(pen); 0406 if (point->activeControlPoint1() && point->activeControlPoint2()) { 0407 QBrush b(Qt::red); 0408 painter.setBrush(b); 0409 } else if (point->activeControlPoint1()) { 0410 QBrush b(Qt::yellow); 0411 painter.setBrush(b); 0412 } else if (point->activeControlPoint2()) { 0413 QBrush b(Qt::darkYellow); 0414 painter.setBrush(b); 0415 } 0416 painter.drawEllipse(r); 0417 } 0418 } 0419 painter.restore(); 0420 debugFlake << "nop =" << i; 0421 } 0422 0423 void KoPathShapePrivate::debugPath() const 0424 { 0425 Q_Q(const KoPathShape); 0426 KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); 0427 for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { 0428 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0429 for (; it != (*pathIt)->constEnd(); ++it) { 0430 debugFlake << "p:" << (*pathIt) << "," << *it << "," << (*it)->point() << "," << (*it)->properties(); 0431 } 0432 } 0433 } 0434 #endif 0435 0436 void KoPathShape::paintPoints(QPainter &painter, const KoViewConverter &converter, int handleRadius) 0437 { 0438 applyConversion(painter, converter); 0439 0440 KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); 0441 0442 for (; pathIt != m_subpaths.constEnd(); ++pathIt) { 0443 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0444 for (; it != (*pathIt)->constEnd(); ++it) 0445 (*it)->paint(painter, handleRadius, KoPathPoint::Node); 0446 } 0447 } 0448 0449 QPainterPath KoPathShape::outline() const 0450 { 0451 QPainterPath path; 0452 foreach(KoSubpath * subpath, m_subpaths) { 0453 KoPathPoint * lastPoint = subpath->first(); 0454 bool activeCP = false; 0455 foreach(KoPathPoint * currPoint, *subpath) { 0456 KoPathPoint::PointProperties currProperties = currPoint->properties(); 0457 if (currPoint == subpath->first()) { 0458 if (currProperties & KoPathPoint::StartSubpath) { 0459 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0460 path.moveTo(currPoint->point()); 0461 } 0462 } else if (activeCP && currPoint->activeControlPoint1()) { 0463 Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); 0464 Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); 0465 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0466 path.cubicTo( 0467 lastPoint->controlPoint2(), 0468 currPoint->controlPoint1(), 0469 currPoint->point()); 0470 } else if (activeCP || currPoint->activeControlPoint1()) { 0471 Q_ASSERT(!qIsNaNPoint(lastPoint->controlPoint2())); 0472 Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); 0473 path.quadTo( 0474 activeCP ? lastPoint->controlPoint2() : currPoint->controlPoint1(), 0475 currPoint->point()); 0476 } else { 0477 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0478 path.lineTo(currPoint->point()); 0479 } 0480 if (currProperties & KoPathPoint::CloseSubpath && currProperties & KoPathPoint::StopSubpath) { 0481 // add curve when there is a curve on the way to the first point 0482 KoPathPoint * firstPoint = subpath->first(); 0483 Q_ASSERT(!qIsNaNPoint(firstPoint->point())); 0484 if (currPoint->activeControlPoint2() && firstPoint->activeControlPoint1()) { 0485 path.cubicTo( 0486 currPoint->controlPoint2(), 0487 firstPoint->controlPoint1(), 0488 firstPoint->point()); 0489 } 0490 else if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { 0491 Q_ASSERT(!qIsNaNPoint(currPoint->point())); 0492 Q_ASSERT(!qIsNaNPoint(currPoint->controlPoint1())); 0493 path.quadTo( 0494 currPoint->activeControlPoint2() ? currPoint->controlPoint2() : firstPoint->controlPoint1(), 0495 firstPoint->point()); 0496 } 0497 path.closeSubpath(); 0498 } 0499 0500 if (currPoint->activeControlPoint2()) { 0501 activeCP = true; 0502 } else { 0503 activeCP = false; 0504 } 0505 lastPoint = currPoint; 0506 } 0507 } 0508 0509 return path; 0510 } 0511 0512 QRectF KoPathShape::boundingRect() const 0513 { 0514 QTransform transform = absoluteTransformation(0); 0515 // calculate the bounding rect of the transformed outline 0516 QRectF bb; 0517 KoShapeStroke *lineBorder = dynamic_cast<KoShapeStroke*>(stroke()); 0518 QPen pen; 0519 if (lineBorder) { 0520 pen.setWidthF(lineBorder->lineWidth()); 0521 } 0522 bb = transform.map(pathStroke(pen)).boundingRect(); 0523 0524 if (stroke()) { 0525 KoInsets inset; 0526 stroke()->strokeInsets(this, inset); 0527 0528 // calculate transformed border insets 0529 QPointF center = transform.map(QPointF()); 0530 QPointF tl = transform.map(QPointF(-inset.left,-inset.top)) - center; 0531 QPointF br = transform.map(QPointF(inset.right,inset.bottom)) -center; 0532 qreal left = qMin(tl.x(),br.x()); 0533 qreal right = qMax(tl.x(),br.x()); 0534 qreal top = qMin(tl.y(),br.y()); 0535 qreal bottom = qMax(tl.y(),br.y()); 0536 bb.adjust(left, top, right, bottom); 0537 } 0538 if (shadow()) { 0539 KoInsets insets; 0540 shadow()->insets(insets); 0541 bb.adjust(-insets.left, -insets.top, insets.right, insets.bottom); 0542 } 0543 if (filterEffectStack()) { 0544 QRectF clipRect = filterEffectStack()->clipRectForBoundingRect(QRectF(QPointF(), size())); 0545 bb |= transform.mapRect(clipRect); 0546 } 0547 return bb; 0548 } 0549 0550 QSizeF KoPathShape::size() const 0551 { 0552 // don't call boundingRect here as it uses absoluteTransformation 0553 // which itself uses size() -> leads to infinite reccursion 0554 return outline().boundingRect().size(); 0555 } 0556 0557 void KoPathShape::setSize(const QSizeF &newSize) 0558 { 0559 Q_D(KoPathShape); 0560 QTransform matrix(resizeMatrix(newSize)); 0561 0562 KoShape::setSize(newSize); 0563 d->map(matrix); 0564 } 0565 0566 QTransform KoPathShape::resizeMatrix(const QSizeF & newSize) const 0567 { 0568 QSizeF oldSize = size(); 0569 if (oldSize.width() == 0.0) { 0570 oldSize.setWidth(0.000001); 0571 } 0572 if (oldSize.height() == 0.0) { 0573 oldSize.setHeight(0.000001); 0574 } 0575 0576 QSizeF sizeNew(newSize); 0577 if (sizeNew.width() == 0.0) { 0578 sizeNew.setWidth(0.000001); 0579 } 0580 if (sizeNew.height() == 0.0) { 0581 sizeNew.setHeight(0.000001); 0582 } 0583 0584 return QTransform(sizeNew.width() / oldSize.width(), 0, 0, sizeNew.height() / oldSize.height(), 0, 0); 0585 } 0586 0587 KoPathPoint * KoPathShape::moveTo(const QPointF &p) 0588 { 0589 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StartSubpath | KoPathPoint::StopSubpath); 0590 KoSubpath * path = new KoSubpath; 0591 path->push_back(point); 0592 m_subpaths.push_back(path); 0593 return point; 0594 } 0595 0596 KoPathPoint * KoPathShape::lineTo(const QPointF &p) 0597 { 0598 Q_D(KoPathShape); 0599 if (m_subpaths.empty()) { 0600 moveTo(QPointF(0, 0)); 0601 } 0602 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); 0603 KoPathPoint * lastPoint = m_subpaths.last()->last(); 0604 d->updateLast(&lastPoint); 0605 m_subpaths.last()->push_back(point); 0606 return point; 0607 } 0608 0609 KoPathPoint * KoPathShape::curveTo(const QPointF &c1, const QPointF &c2, const QPointF &p) 0610 { 0611 Q_D(KoPathShape); 0612 if (m_subpaths.empty()) { 0613 moveTo(QPointF(0, 0)); 0614 } 0615 KoPathPoint * lastPoint = m_subpaths.last()->last(); 0616 d->updateLast(&lastPoint); 0617 lastPoint->setControlPoint2(c1); 0618 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); 0619 point->setControlPoint1(c2); 0620 m_subpaths.last()->push_back(point); 0621 return point; 0622 } 0623 0624 KoPathPoint * KoPathShape::curveTo(const QPointF &c, const QPointF &p) 0625 { 0626 Q_D(KoPathShape); 0627 if (m_subpaths.empty()) 0628 moveTo(QPointF(0, 0)); 0629 0630 KoPathPoint * lastPoint = m_subpaths.last()->last(); 0631 d->updateLast(&lastPoint); 0632 lastPoint->setControlPoint2(c); 0633 KoPathPoint * point = new KoPathPoint(this, p, KoPathPoint::StopSubpath); 0634 m_subpaths.last()->push_back(point); 0635 0636 return point; 0637 } 0638 0639 KoPathPoint * KoPathShape::arcTo(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle) 0640 { 0641 if (m_subpaths.empty()) { 0642 moveTo(QPointF(0, 0)); 0643 } 0644 0645 KoPathPoint * lastPoint = m_subpaths.last()->last(); 0646 if (lastPoint->properties() & KoPathPoint::CloseSubpath) { 0647 lastPoint = m_subpaths.last()->first(); 0648 } 0649 QPointF startpoint(lastPoint->point()); 0650 0651 KoPathPoint * newEndPoint = lastPoint; 0652 0653 QPointF curvePoints[12]; 0654 int pointCnt = arcToCurve(rx, ry, startAngle, sweepAngle, startpoint, curvePoints); 0655 for (int i = 0; i < pointCnt; i += 3) { 0656 newEndPoint = curveTo(curvePoints[i], curvePoints[i+1], curvePoints[i+2]); 0657 } 0658 return newEndPoint; 0659 } 0660 0661 int KoPathShape::arcToCurve(qreal rx, qreal ry, qreal startAngle, qreal sweepAngle, const QPointF & offset, QPointF * curvePoints) const 0662 { 0663 int pointCnt = 0; 0664 0665 // check Parameters 0666 if (sweepAngle == 0) 0667 return pointCnt; 0668 if (sweepAngle > 360) 0669 sweepAngle = 360; 0670 else if (sweepAngle < -360) 0671 sweepAngle = - 360; 0672 0673 if (rx == 0 || ry == 0) { 0674 //TODO 0675 } 0676 0677 // split angles bigger than 90° so that it gives a good approximation to the circle 0678 qreal parts = ceil(qAbs(sweepAngle / 90.0)); 0679 0680 qreal sa_rad = startAngle * M_PI / 180.0; 0681 qreal partangle = sweepAngle / parts; 0682 qreal endangle = startAngle + partangle; 0683 qreal se_rad = endangle * M_PI / 180.0; 0684 qreal sinsa = sin(sa_rad); 0685 qreal cossa = cos(sa_rad); 0686 qreal kappa = 4.0 / 3.0 * tan((se_rad - sa_rad) / 4); 0687 0688 // startpoint is at the last point is the path but when it is closed 0689 // it is at the first point 0690 QPointF startpoint(offset); 0691 0692 //center berechnen 0693 QPointF center(startpoint - QPointF(cossa * rx, -sinsa * ry)); 0694 0695 //debugFlake <<"kappa" << kappa <<"parts" << parts; 0696 0697 for (int part = 0; part < parts; ++part) { 0698 // start tangent 0699 curvePoints[pointCnt++] = QPointF(startpoint - QPointF(sinsa * rx * kappa, cossa * ry * kappa)); 0700 0701 qreal sinse = sin(se_rad); 0702 qreal cosse = cos(se_rad); 0703 0704 // end point 0705 QPointF endpoint(center + QPointF(cosse * rx, -sinse * ry)); 0706 // end tangent 0707 curvePoints[pointCnt++] = QPointF(endpoint - QPointF(-sinse * rx * kappa, -cosse * ry * kappa)); 0708 curvePoints[pointCnt++] = endpoint; 0709 0710 // set the endpoint as next start point 0711 startpoint = endpoint; 0712 sinsa = sinse; 0713 cossa = cosse; 0714 endangle += partangle; 0715 se_rad = endangle * M_PI / 180.0; 0716 } 0717 0718 return pointCnt; 0719 } 0720 0721 void KoPathShape::close() 0722 { 0723 Q_D(KoPathShape); 0724 if (m_subpaths.empty()) { 0725 return; 0726 } 0727 d->closeSubpath(m_subpaths.last()); 0728 } 0729 0730 void KoPathShape::closeMerge() 0731 { 0732 Q_D(KoPathShape); 0733 if (m_subpaths.empty()) { 0734 return; 0735 } 0736 d->closeMergeSubpath(m_subpaths.last()); 0737 } 0738 0739 QPointF KoPathShape::normalize() 0740 { 0741 Q_D(KoPathShape); 0742 QPointF tl(outline().boundingRect().topLeft()); 0743 QTransform matrix; 0744 matrix.translate(-tl.x(), -tl.y()); 0745 d->map(matrix); 0746 0747 // keep the top left point of the object 0748 applyTransformation(matrix.inverted()); 0749 d->shapeChanged(ContentChanged); 0750 return tl; 0751 } 0752 0753 void KoPathShapePrivate::map(const QTransform &matrix) 0754 { 0755 Q_Q(KoPathShape); 0756 KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); 0757 for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { 0758 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0759 for (; it != (*pathIt)->constEnd(); ++it) { 0760 (*it)->map(matrix); 0761 } 0762 } 0763 } 0764 0765 void KoPathShapePrivate::updateLast(KoPathPoint **lastPoint) 0766 { 0767 Q_Q(KoPathShape); 0768 // check if we are about to add a new point to a closed subpath 0769 if ((*lastPoint)->properties() & KoPathPoint::StopSubpath 0770 && (*lastPoint)->properties() & KoPathPoint::CloseSubpath) { 0771 // get the first point of the subpath 0772 KoPathPoint *subpathStart = q->m_subpaths.last()->first(); 0773 // clone the first point of the subpath... 0774 KoPathPoint * newLastPoint = new KoPathPoint(*subpathStart); 0775 // ... and make it a normal point 0776 newLastPoint->setProperties(KoPathPoint::Normal); 0777 // now start a new subpath with the cloned start point 0778 KoSubpath *path = new KoSubpath; 0779 path->push_back(newLastPoint); 0780 q->m_subpaths.push_back(path); 0781 *lastPoint = newLastPoint; 0782 } else { 0783 // the subpath was not closed so the formerly last point 0784 // of the subpath is no end point anymore 0785 (*lastPoint)->unsetProperty(KoPathPoint::StopSubpath); 0786 } 0787 (*lastPoint)->unsetProperty(KoPathPoint::CloseSubpath); 0788 } 0789 0790 QList<KoPathPoint*> KoPathShape::pointsAt(const QRectF &r) const 0791 { 0792 QList<KoPathPoint*> result; 0793 0794 KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); 0795 for (; pathIt != m_subpaths.constEnd(); ++pathIt) { 0796 KoSubpath::const_iterator it((*pathIt)->constBegin()); 0797 for (; it != (*pathIt)->constEnd(); ++it) { 0798 if (r.contains((*it)->point())) 0799 result.append(*it); 0800 else if ((*it)->activeControlPoint1() && r.contains((*it)->controlPoint1())) 0801 result.append(*it); 0802 else if ((*it)->activeControlPoint2() && r.contains((*it)->controlPoint2())) 0803 result.append(*it); 0804 } 0805 } 0806 return result; 0807 } 0808 0809 QList<KoPathSegment> KoPathShape::segmentsAt(const QRectF &r) const 0810 { 0811 QList<KoPathSegment> segments; 0812 int subpathCount = m_subpaths.count(); 0813 for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { 0814 KoSubpath * subpath = m_subpaths[subpathIndex]; 0815 int pointCount = subpath->count(); 0816 bool subpathClosed = isClosedSubpath(subpathIndex); 0817 for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { 0818 if (pointIndex == (pointCount - 1) && ! subpathClosed) 0819 break; 0820 KoPathSegment s(subpath->at(pointIndex), subpath->at((pointIndex + 1) % pointCount)); 0821 QRectF controlRect = s.controlPointRect(); 0822 if (! r.intersects(controlRect) && ! controlRect.contains(r)) 0823 continue; 0824 QRectF bound = s.boundingRect(); 0825 if (! r.intersects(bound) && ! bound.contains(r)) 0826 continue; 0827 0828 segments.append(s); 0829 } 0830 } 0831 return segments; 0832 } 0833 0834 KoPathPointIndex KoPathShape::pathPointIndex(const KoPathPoint *point) const 0835 { 0836 for (int subpathIndex = 0; subpathIndex < m_subpaths.size(); ++subpathIndex) { 0837 KoSubpath * subpath = m_subpaths.at(subpathIndex); 0838 for (int pointPos = 0; pointPos < subpath->size(); ++pointPos) { 0839 if (subpath->at(pointPos) == point) { 0840 return KoPathPointIndex(subpathIndex, pointPos); 0841 } 0842 } 0843 } 0844 return KoPathPointIndex(-1, -1); 0845 } 0846 0847 KoPathPoint * KoPathShape::pointByIndex(const KoPathPointIndex &pointIndex) const 0848 { 0849 Q_D(const KoPathShape); 0850 KoSubpath *subpath = d->subPath(pointIndex.first); 0851 0852 if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) 0853 return 0; 0854 0855 return subpath->at(pointIndex.second); 0856 } 0857 0858 KoPathSegment KoPathShape::segmentByIndex(const KoPathPointIndex &pointIndex) const 0859 { 0860 Q_D(const KoPathShape); 0861 KoPathSegment segment(0, 0); 0862 0863 KoSubpath *subpath = d->subPath(pointIndex.first); 0864 0865 if (subpath != 0 && pointIndex.second >= 0 && pointIndex.second < subpath->size()) { 0866 KoPathPoint * point = subpath->at(pointIndex.second); 0867 int index = pointIndex.second; 0868 // check if we have a (closing) segment starting from the last point 0869 if ((index == subpath->size() - 1) && point->properties() & KoPathPoint::CloseSubpath) 0870 index = 0; 0871 else 0872 ++index; 0873 0874 if (index < subpath->size()) { 0875 segment = KoPathSegment(point, subpath->at(index)); 0876 } 0877 } 0878 return segment; 0879 } 0880 0881 int KoPathShape::pointCount() const 0882 { 0883 int i = 0; 0884 KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); 0885 for (; pathIt != m_subpaths.constEnd(); ++pathIt) { 0886 i += (*pathIt)->size(); 0887 } 0888 0889 return i; 0890 } 0891 0892 int KoPathShape::subpathCount() const 0893 { 0894 return m_subpaths.count(); 0895 } 0896 0897 int KoPathShape::subpathPointCount(int subpathIndex) const 0898 { 0899 Q_D(const KoPathShape); 0900 KoSubpath *subpath = d->subPath(subpathIndex); 0901 0902 if (subpath == 0) 0903 return -1; 0904 0905 return subpath->size(); 0906 } 0907 0908 bool KoPathShape::isClosedSubpath(int subpathIndex) const 0909 { 0910 Q_D(const KoPathShape); 0911 KoSubpath *subpath = d->subPath(subpathIndex); 0912 0913 if (subpath == 0) 0914 return false; 0915 0916 const bool firstClosed = subpath->first()->properties() & KoPathPoint::CloseSubpath; 0917 const bool lastClosed = subpath->last()->properties() & KoPathPoint::CloseSubpath; 0918 0919 return firstClosed && lastClosed; 0920 } 0921 0922 bool KoPathShape::insertPoint(KoPathPoint* point, const KoPathPointIndex &pointIndex) 0923 { 0924 Q_D(KoPathShape); 0925 KoSubpath *subpath = d->subPath(pointIndex.first); 0926 0927 if (subpath == 0 || pointIndex.second < 0 || pointIndex.second > subpath->size()) 0928 return false; 0929 0930 KoPathPoint::PointProperties properties = point->properties(); 0931 properties &= ~KoPathPoint::StartSubpath; 0932 properties &= ~KoPathPoint::StopSubpath; 0933 properties &= ~KoPathPoint::CloseSubpath; 0934 // check if new point starts subpath 0935 if (pointIndex.second == 0) { 0936 properties |= KoPathPoint::StartSubpath; 0937 // subpath was closed 0938 if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { 0939 // keep the path closed 0940 properties |= KoPathPoint::CloseSubpath; 0941 } 0942 // old first point does not start the subpath anymore 0943 subpath->first()->unsetProperty(KoPathPoint::StartSubpath); 0944 } 0945 // check if new point stops subpath 0946 else if (pointIndex.second == subpath->size()) { 0947 properties |= KoPathPoint::StopSubpath; 0948 // subpath was closed 0949 if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { 0950 // keep the path closed 0951 properties = properties | KoPathPoint::CloseSubpath; 0952 } 0953 // old last point does not end subpath anymore 0954 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 0955 } 0956 0957 point->setProperties(properties); 0958 point->setParent(this); 0959 subpath->insert(pointIndex.second , point); 0960 return true; 0961 } 0962 0963 KoPathPoint * KoPathShape::removePoint(const KoPathPointIndex &pointIndex) 0964 { 0965 Q_D(KoPathShape); 0966 KoSubpath *subpath = d->subPath(pointIndex.first); 0967 0968 if (subpath == 0 || pointIndex.second < 0 || pointIndex.second >= subpath->size()) 0969 return 0; 0970 0971 KoPathPoint * point = subpath->takeAt(pointIndex.second); 0972 0973 //don't do anything (not even crash), if there was only one point 0974 if (pointCount()==0) { 0975 return point; 0976 } 0977 // check if we removed the first point 0978 else if (pointIndex.second == 0) { 0979 // first point removed, set new StartSubpath 0980 subpath->first()->setProperty(KoPathPoint::StartSubpath); 0981 // check if path was closed 0982 if (subpath->last()->properties() & KoPathPoint::CloseSubpath) { 0983 // keep path closed 0984 subpath->first()->setProperty(KoPathPoint::CloseSubpath); 0985 } 0986 } 0987 // check if we removed the last point 0988 else if (pointIndex.second == subpath->size()) { // use size as point is already removed 0989 // last point removed, set new StopSubpath 0990 subpath->last()->setProperty(KoPathPoint::StopSubpath); 0991 // check if path was closed 0992 if (point->properties() & KoPathPoint::CloseSubpath) { 0993 // keep path closed 0994 subpath->last()->setProperty(KoPathPoint::CloseSubpath); 0995 } 0996 } 0997 0998 return point; 0999 } 1000 1001 bool KoPathShape::breakAfter(const KoPathPointIndex &pointIndex) 1002 { 1003 Q_D(KoPathShape); 1004 KoSubpath *subpath = d->subPath(pointIndex.first); 1005 1006 if (!subpath || pointIndex.second < 0 || pointIndex.second > subpath->size() - 2 1007 || isClosedSubpath(pointIndex.first)) 1008 return false; 1009 1010 KoSubpath * newSubpath = new KoSubpath; 1011 1012 int size = subpath->size(); 1013 for (int i = pointIndex.second + 1; i < size; ++i) { 1014 newSubpath->append(subpath->takeAt(pointIndex.second + 1)); 1015 } 1016 // now make the first point of the new subpath a starting node 1017 newSubpath->first()->setProperty(KoPathPoint::StartSubpath); 1018 // the last point of the old subpath is now an ending node 1019 subpath->last()->setProperty(KoPathPoint::StopSubpath); 1020 1021 // insert the new subpath after the broken one 1022 m_subpaths.insert(pointIndex.first + 1, newSubpath); 1023 1024 return true; 1025 } 1026 1027 bool KoPathShape::join(int subpathIndex) 1028 { 1029 Q_D(KoPathShape); 1030 KoSubpath *subpath = d->subPath(subpathIndex); 1031 KoSubpath *nextSubpath = d->subPath(subpathIndex + 1); 1032 1033 if (!subpath || !nextSubpath || isClosedSubpath(subpathIndex) 1034 || isClosedSubpath(subpathIndex+1)) 1035 return false; 1036 1037 // the last point of the subpath does not end the subpath anymore 1038 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 1039 // the first point of the next subpath does not start a subpath anymore 1040 nextSubpath->first()->unsetProperty(KoPathPoint::StartSubpath); 1041 1042 // append the second subpath to the first 1043 foreach(KoPathPoint * p, *nextSubpath) 1044 subpath->append(p); 1045 1046 // remove the nextSubpath from path 1047 m_subpaths.removeAt(subpathIndex + 1); 1048 1049 // delete it as it is no longer possible to use it 1050 delete nextSubpath; 1051 1052 return true; 1053 } 1054 1055 bool KoPathShape::moveSubpath(int oldSubpathIndex, int newSubpathIndex) 1056 { 1057 Q_D(KoPathShape); 1058 KoSubpath *subpath = d->subPath(oldSubpathIndex); 1059 1060 if (subpath == 0 || newSubpathIndex >= m_subpaths.size()) 1061 return false; 1062 1063 if (oldSubpathIndex == newSubpathIndex) 1064 return true; 1065 1066 m_subpaths.removeAt(oldSubpathIndex); 1067 m_subpaths.insert(newSubpathIndex, subpath); 1068 1069 return true; 1070 } 1071 1072 KoPathPointIndex KoPathShape::openSubpath(const KoPathPointIndex &pointIndex) 1073 { 1074 Q_D(KoPathShape); 1075 KoSubpath *subpath = d->subPath(pointIndex.first); 1076 1077 if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() 1078 || !isClosedSubpath(pointIndex.first)) 1079 return KoPathPointIndex(-1, -1); 1080 1081 KoPathPoint * oldStartPoint = subpath->first(); 1082 // the old starting node no longer starts the subpath 1083 oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); 1084 // the old end node no longer closes the subpath 1085 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 1086 1087 // reorder the subpath 1088 for (int i = 0; i < pointIndex.second; ++i) { 1089 subpath->append(subpath->takeFirst()); 1090 } 1091 // make the first point a start node 1092 subpath->first()->setProperty(KoPathPoint::StartSubpath); 1093 // make the last point an end node 1094 subpath->last()->setProperty(KoPathPoint::StopSubpath); 1095 1096 return pathPointIndex(oldStartPoint); 1097 } 1098 1099 KoPathPointIndex KoPathShape::closeSubpath(const KoPathPointIndex &pointIndex) 1100 { 1101 Q_D(KoPathShape); 1102 KoSubpath *subpath = d->subPath(pointIndex.first); 1103 1104 if (!subpath || pointIndex.second < 0 || pointIndex.second >= subpath->size() 1105 || isClosedSubpath(pointIndex.first)) 1106 return KoPathPointIndex(-1, -1); 1107 1108 KoPathPoint * oldStartPoint = subpath->first(); 1109 // the old starting node no longer starts the subpath 1110 oldStartPoint->unsetProperty(KoPathPoint::StartSubpath); 1111 // the old end node no longer ends the subpath 1112 subpath->last()->unsetProperty(KoPathPoint::StopSubpath); 1113 1114 // reorder the subpath 1115 for (int i = 0; i < pointIndex.second; ++i) { 1116 subpath->append(subpath->takeFirst()); 1117 } 1118 subpath->first()->setProperty(KoPathPoint::StartSubpath); 1119 subpath->last()->setProperty(KoPathPoint::StopSubpath); 1120 1121 d->closeSubpath(subpath); 1122 return pathPointIndex(oldStartPoint); 1123 } 1124 1125 bool KoPathShape::reverseSubpath(int subpathIndex) 1126 { 1127 Q_D(KoPathShape); 1128 KoSubpath *subpath = d->subPath(subpathIndex); 1129 1130 if (subpath == 0) 1131 return false; 1132 1133 int size = subpath->size(); 1134 for (int i = 0; i < size; ++i) { 1135 KoPathPoint *p = subpath->takeAt(i); 1136 p->reverse(); 1137 subpath->prepend(p); 1138 } 1139 1140 // adjust the position dependent properties 1141 KoPathPoint *first = subpath->first(); 1142 KoPathPoint *last = subpath->last(); 1143 1144 KoPathPoint::PointProperties firstProps = first->properties(); 1145 KoPathPoint::PointProperties lastProps = last->properties(); 1146 1147 firstProps |= KoPathPoint::StartSubpath; 1148 firstProps &= ~KoPathPoint::StopSubpath; 1149 lastProps |= KoPathPoint::StopSubpath; 1150 lastProps &= ~KoPathPoint::StartSubpath; 1151 if (firstProps & KoPathPoint::CloseSubpath) { 1152 firstProps |= KoPathPoint::CloseSubpath; 1153 lastProps |= KoPathPoint::CloseSubpath; 1154 } 1155 first->setProperties(firstProps); 1156 last->setProperties(lastProps); 1157 1158 return true; 1159 } 1160 1161 KoSubpath * KoPathShape::removeSubpath(int subpathIndex) 1162 { 1163 Q_D(KoPathShape); 1164 KoSubpath *subpath = d->subPath(subpathIndex); 1165 1166 if (subpath != 0) 1167 m_subpaths.removeAt(subpathIndex); 1168 1169 return subpath; 1170 } 1171 1172 bool KoPathShape::addSubpath(KoSubpath * subpath, int subpathIndex) 1173 { 1174 if (subpathIndex < 0 || subpathIndex > m_subpaths.size()) 1175 return false; 1176 1177 m_subpaths.insert(subpathIndex, subpath); 1178 1179 return true; 1180 } 1181 1182 bool KoPathShape::combine(KoPathShape *path) 1183 { 1184 if (! path) 1185 return false; 1186 1187 QTransform pathMatrix = path->absoluteTransformation(0); 1188 QTransform myMatrix = absoluteTransformation(0).inverted(); 1189 1190 foreach(KoSubpath* subpath, path->m_subpaths) { 1191 KoSubpath *newSubpath = new KoSubpath(); 1192 1193 foreach(KoPathPoint* point, *subpath) { 1194 KoPathPoint *newPoint = new KoPathPoint(*point); 1195 newPoint->map(pathMatrix); 1196 newPoint->map(myMatrix); 1197 newPoint->setParent(this); 1198 newSubpath->append(newPoint); 1199 } 1200 m_subpaths.append(newSubpath); 1201 } 1202 normalize(); 1203 return true; 1204 } 1205 1206 bool KoPathShape::separate(QList<KoPathShape*> & separatedPaths) 1207 { 1208 if (! m_subpaths.size()) 1209 return false; 1210 1211 QTransform myMatrix = absoluteTransformation(0); 1212 1213 foreach(KoSubpath* subpath, m_subpaths) { 1214 KoPathShape *shape = new KoPathShape(); 1215 if (! shape) continue; 1216 1217 shape->setStroke(stroke()); 1218 shape->setShapeId(shapeId()); 1219 1220 KoSubpath *newSubpath = new KoSubpath(); 1221 1222 foreach(KoPathPoint* point, *subpath) { 1223 KoPathPoint *newPoint = new KoPathPoint(*point); 1224 newPoint->map(myMatrix); 1225 newSubpath->append(newPoint); 1226 } 1227 shape->m_subpaths.append(newSubpath); 1228 shape->normalize(); 1229 separatedPaths.append(shape); 1230 } 1231 return true; 1232 } 1233 1234 void KoPathShapePrivate::closeSubpath(KoSubpath *subpath) 1235 { 1236 if (! subpath) 1237 return; 1238 1239 subpath->last()->setProperty(KoPathPoint::CloseSubpath); 1240 subpath->first()->setProperty(KoPathPoint::CloseSubpath); 1241 } 1242 1243 void KoPathShapePrivate::closeMergeSubpath(KoSubpath *subpath) 1244 { 1245 if (! subpath || subpath->size() < 2) 1246 return; 1247 1248 KoPathPoint * lastPoint = subpath->last(); 1249 KoPathPoint * firstPoint = subpath->first(); 1250 1251 // check if first and last points are coincident 1252 if (lastPoint->point() == firstPoint->point()) { 1253 // we are removing the current last point and 1254 // reuse its first control point if active 1255 firstPoint->setProperty(KoPathPoint::StartSubpath); 1256 firstPoint->setProperty(KoPathPoint::CloseSubpath); 1257 if (lastPoint->activeControlPoint1()) 1258 firstPoint->setControlPoint1(lastPoint->controlPoint1()); 1259 // remove last point 1260 delete subpath->takeLast(); 1261 // the new last point closes the subpath now 1262 lastPoint = subpath->last(); 1263 lastPoint->setProperty(KoPathPoint::StopSubpath); 1264 lastPoint->setProperty(KoPathPoint::CloseSubpath); 1265 } else { 1266 closeSubpath(subpath); 1267 } 1268 } 1269 1270 KoSubpath *KoPathShapePrivate::subPath(int subpathIndex) const 1271 { 1272 Q_Q(const KoPathShape); 1273 if (subpathIndex < 0 || subpathIndex >= q->m_subpaths.size()) 1274 return 0; 1275 1276 return q->m_subpaths.at(subpathIndex); 1277 } 1278 1279 QString KoPathShape::pathShapeId() const 1280 { 1281 return KoPathShapeId; 1282 } 1283 1284 QString KoPathShape::toString(const QTransform &matrix) const 1285 { 1286 QString d; 1287 1288 // iterate over all subpaths 1289 KoSubpathList::const_iterator pathIt(m_subpaths.constBegin()); 1290 for (; pathIt != m_subpaths.constEnd(); ++pathIt) { 1291 KoSubpath::const_iterator pointIt((*pathIt)->constBegin()); 1292 // keep a pointer to the first point of the subpath 1293 KoPathPoint *firstPoint(*pointIt); 1294 // keep a pointer to the previous point of the subpath 1295 KoPathPoint *lastPoint = firstPoint; 1296 // keep track if the previous point has an active control point 2 1297 bool activeControlPoint2 = false; 1298 1299 // iterate over all points of the current subpath 1300 for (; pointIt != (*pathIt)->constEnd(); ++pointIt) { 1301 KoPathPoint *currPoint(*pointIt); 1302 // first point of subpath ? 1303 if (currPoint == firstPoint) { 1304 // are we starting a subpath ? 1305 if (currPoint->properties() & KoPathPoint::StartSubpath) { 1306 const QPointF p = matrix.map(currPoint->point()); 1307 d += QString("M%1 %2").arg(p.x()).arg(p.y()); 1308 } 1309 } 1310 // end point of curve segment ? 1311 else if (activeControlPoint2 || currPoint->activeControlPoint1()) { 1312 // check if we have a cubic or quadratic curve 1313 const bool isCubic = activeControlPoint2 && currPoint->activeControlPoint1(); 1314 KoPathSegment cubicSeg = isCubic ? KoPathSegment(lastPoint, currPoint) 1315 : KoPathSegment(lastPoint, currPoint).toCubic(); 1316 const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); 1317 const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); 1318 const QPointF p = matrix.map(cubicSeg.second()->point()); 1319 d += QString("C%1 %2 %3 %4 %5 %6") 1320 .arg(cp1.x()).arg(cp1.y()) 1321 .arg(cp2.x()).arg(cp2.y()) 1322 .arg(p.x()).arg(p.y()); 1323 } 1324 // end point of line segment! 1325 else { 1326 const QPointF p = matrix.map(currPoint->point()); 1327 d += QString("L%1 %2").arg(p.x()).arg(p.y()); 1328 } 1329 // last point closes subpath ? 1330 if (currPoint->properties() & KoPathPoint::StopSubpath 1331 && currPoint->properties() & KoPathPoint::CloseSubpath) { 1332 // add curve when there is a curve on the way to the first point 1333 if (currPoint->activeControlPoint2() || firstPoint->activeControlPoint1()) { 1334 // check if we have a cubic or quadratic curve 1335 const bool isCubic = currPoint->activeControlPoint2() && firstPoint->activeControlPoint1(); 1336 KoPathSegment cubicSeg = isCubic ? KoPathSegment(currPoint, firstPoint) 1337 : KoPathSegment(currPoint, firstPoint).toCubic(); 1338 const QPointF cp1 = matrix.map(cubicSeg.first()->controlPoint2()); 1339 const QPointF cp2 = matrix.map(cubicSeg.second()->controlPoint1()); 1340 const QPointF p = matrix.map(cubicSeg.second()->point()); 1341 d += QString("C%1 %2 %3 %4 %5 %6") 1342 .arg(cp1.x()).arg(cp1.y()) 1343 .arg(cp2.x()).arg(cp2.y()) 1344 .arg(p.x()).arg(p.y()); 1345 } 1346 d += QString("Z"); 1347 } 1348 1349 activeControlPoint2 = currPoint->activeControlPoint2(); 1350 lastPoint = currPoint; 1351 } 1352 } 1353 1354 return d; 1355 } 1356 1357 char nodeType(const KoPathPoint * point) 1358 { 1359 if (point->properties() & KoPathPoint::IsSmooth) { 1360 return 's'; 1361 } 1362 else if (point->properties() & KoPathPoint::IsSymmetric) { 1363 return 'z'; 1364 } 1365 else { 1366 return 'c'; 1367 } 1368 } 1369 1370 QString KoPathShapePrivate::nodeTypes() const 1371 { 1372 Q_Q(const KoPathShape); 1373 QString types; 1374 KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); 1375 for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { 1376 KoSubpath::const_iterator it((*pathIt)->constBegin()); 1377 for (; it != (*pathIt)->constEnd(); ++it) { 1378 if (it == (*pathIt)->constBegin()) { 1379 types.append('c'); 1380 } 1381 else { 1382 types.append(nodeType(*it)); 1383 } 1384 1385 if ((*it)->properties() & KoPathPoint::StopSubpath 1386 && (*it)->properties() & KoPathPoint::CloseSubpath) { 1387 KoPathPoint * firstPoint = (*pathIt)->first(); 1388 types.append(nodeType(firstPoint)); 1389 } 1390 } 1391 } 1392 return types; 1393 } 1394 1395 void updateNodeType(KoPathPoint * point, const QChar & nodeType) 1396 { 1397 if (nodeType == 's') { 1398 point->setProperty(KoPathPoint::IsSmooth); 1399 } 1400 else if (nodeType == 'z') { 1401 point->setProperty(KoPathPoint::IsSymmetric); 1402 } 1403 } 1404 1405 void KoPathShapePrivate::loadNodeTypes(const KoXmlElement &element) 1406 { 1407 Q_Q(KoPathShape); 1408 if (element.hasAttributeNS(KoXmlNS::calligra, "nodeTypes")) { 1409 QString nodeTypes = element.attributeNS(KoXmlNS::calligra, "nodeTypes"); 1410 QString::const_iterator nIt(nodeTypes.constBegin()); 1411 KoSubpathList::const_iterator pathIt(q->m_subpaths.constBegin()); 1412 for (; pathIt != q->m_subpaths.constEnd(); ++pathIt) { 1413 KoSubpath::const_iterator it((*pathIt)->constBegin()); 1414 for (; it != (*pathIt)->constEnd(); ++it, nIt++) { 1415 // be sure not to crash if there are not enough nodes in nodeTypes 1416 if (nIt == nodeTypes.constEnd()) { 1417 warnFlake << "not enough nodes in calligra:nodeTypes"; 1418 return; 1419 } 1420 // the first node is always of type 'c' 1421 if (it != (*pathIt)->constBegin()) { 1422 updateNodeType(*it, *nIt); 1423 } 1424 1425 if ((*it)->properties() & KoPathPoint::StopSubpath 1426 && (*it)->properties() & KoPathPoint::CloseSubpath) { 1427 ++nIt; 1428 updateNodeType((*pathIt)->first(), *nIt); 1429 } 1430 } 1431 } 1432 } 1433 } 1434 1435 Qt::FillRule KoPathShape::fillRule() const 1436 { 1437 Q_D(const KoPathShape); 1438 return d->fillRule; 1439 } 1440 1441 void KoPathShape::setFillRule(Qt::FillRule fillRule) 1442 { 1443 Q_D(KoPathShape); 1444 d->fillRule = fillRule; 1445 } 1446 1447 KoPathShape * KoPathShape::createShapeFromPainterPath(const QPainterPath &path) 1448 { 1449 KoPathShape * shape = new KoPathShape(); 1450 1451 int elementCount = path.elementCount(); 1452 for (int i = 0; i < elementCount; i++) { 1453 QPainterPath::Element element = path.elementAt(i); 1454 switch (element.type) { 1455 case QPainterPath::MoveToElement: 1456 shape->moveTo(QPointF(element.x, element.y)); 1457 break; 1458 case QPainterPath::LineToElement: 1459 shape->lineTo(QPointF(element.x, element.y)); 1460 break; 1461 case QPainterPath::CurveToElement: 1462 shape->curveTo(QPointF(element.x, element.y), 1463 QPointF(path.elementAt(i + 1).x, path.elementAt(i + 1).y), 1464 QPointF(path.elementAt(i + 2).x, path.elementAt(i + 2).y)); 1465 break; 1466 default: 1467 continue; 1468 } 1469 } 1470 1471 shape->normalize(); 1472 return shape; 1473 } 1474 1475 bool KoPathShape::hitTest(const QPointF &position) const 1476 { 1477 if (parent() && parent()->isClipped(this) && ! parent()->hitTest(position)) 1478 return false; 1479 1480 QPointF point = absoluteTransformation(0).inverted().map(position); 1481 const QPainterPath outlinePath = outline(); 1482 if (stroke()) { 1483 KoInsets insets; 1484 stroke()->strokeInsets(this, insets); 1485 QRectF roi(QPointF(-insets.left, -insets.top), QPointF(insets.right, insets.bottom)); 1486 roi.moveCenter(point); 1487 if (outlinePath.intersects(roi) || outlinePath.contains(roi)) 1488 return true; 1489 } else { 1490 if (outlinePath.contains(point)) 1491 return true; 1492 } 1493 1494 // if there is no shadow we can as well just leave 1495 if (! shadow()) 1496 return false; 1497 1498 // the shadow has an offset to the shape, so we simply 1499 // check if the position minus the shadow offset hits the shape 1500 point = absoluteTransformation(0).inverted().map(position - shadow()->offset()); 1501 1502 return outlinePath.contains(point); 1503 } 1504 1505 void KoPathShape::setMarker(const KoMarkerData &markerData) 1506 { 1507 Q_D(KoPathShape); 1508 1509 if (markerData.position() == KoMarkerData::MarkerStart) { 1510 d->startMarker = markerData; 1511 } 1512 else { 1513 d->endMarker = markerData; 1514 } 1515 } 1516 1517 void KoPathShape::setMarker(KoMarker *marker, KoMarkerData::MarkerPosition position) 1518 { 1519 Q_D(KoPathShape); 1520 1521 if (position == KoMarkerData::MarkerStart) { 1522 if (!d->startMarker.marker()) { 1523 d->startMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0)); 1524 } 1525 d->startMarker.setMarker(marker); 1526 } 1527 else { 1528 if (!d->endMarker.marker()) { 1529 d->endMarker.setWidth(MM_TO_POINT(DefaultMarkerWidth), qreal(0.0)); 1530 } 1531 d->endMarker.setMarker(marker); 1532 } 1533 } 1534 1535 KoMarker *KoPathShape::marker(KoMarkerData::MarkerPosition position) const 1536 { 1537 Q_D(const KoPathShape); 1538 1539 if (position == KoMarkerData::MarkerStart) { 1540 return d->startMarker.marker(); 1541 } 1542 else { 1543 return d->endMarker.marker(); 1544 } 1545 } 1546 1547 KoMarkerData KoPathShape::markerData(KoMarkerData::MarkerPosition position) const 1548 { 1549 Q_D(const KoPathShape); 1550 1551 if (position == KoMarkerData::MarkerStart) { 1552 return d->startMarker; 1553 } 1554 else { 1555 return d->endMarker; 1556 } 1557 } 1558 1559 QPainterPath KoPathShape::pathStroke(const QPen &pen) const 1560 { 1561 if (m_subpaths.isEmpty()) { 1562 return QPainterPath(); 1563 } 1564 QPainterPath pathOutline; 1565 1566 QPainterPathStroker stroker; 1567 stroker.setWidth(0); 1568 stroker.setJoinStyle(Qt::MiterJoin); 1569 1570 QPair<KoPathSegment, KoPathSegment> firstSegments; 1571 QPair<KoPathSegment, KoPathSegment> lastSegments; 1572 1573 KoPathPoint *firstPoint = 0; 1574 KoPathPoint *lastPoint = 0; 1575 KoPathPoint *secondPoint = 0; 1576 KoPathPoint *preLastPoint = 0; 1577 1578 KoSubpath *firstSubpath = m_subpaths.first(); 1579 bool twoPointPath = subpathPointCount(0) == 2; 1580 bool closedPath = isClosedSubpath(0); 1581 1582 /* 1583 * The geometry is horizontally centered. It is vertically positioned relative to an offset value which 1584 * is specified by a draw:marker-start-center attribute for markers referenced by a 1585 * draw:marker-start attribute, and by the draw:marker-end-center attribute for markers 1586 * referenced by a draw:marker-end attribute. The attribute value true defines an offset of 0.5 1587 * and the attribute value false defines an offset of 0.3, which is also the default value. The offset 1588 * specifies the marker's vertical position in a range from 0.0 to 1.0, where the value 0.0 means the 1589 * geometry's bottom bound is aligned to the X axis of the local coordinate system of the marker 1590 * geometry, and where the value 1.0 means the top bound to be aligned to the X axis of the local 1591 * coordinate system of the marker geometry. 1592 * 1593 * The shorten factor to use results of the 0.3 which means we need to start at 0.7 * height of the marker 1594 */ 1595 static const qreal shortenFactor = 0.7; 1596 1597 KoMarkerData mdStart = markerData(KoMarkerData::MarkerStart); 1598 KoMarkerData mdEnd = markerData(KoMarkerData::MarkerEnd); 1599 if (mdStart.marker() && !closedPath) { 1600 QPainterPath markerPath = mdStart.marker()->path(mdStart.width(pen.widthF())); 1601 1602 KoPathSegment firstSegment = segmentByIndex(KoPathPointIndex(0, 0)); 1603 if (firstSegment.isValid()) { 1604 QRectF pathBoundingRect = markerPath.boundingRect(); 1605 qreal shortenLength = pathBoundingRect.height() * shortenFactor; 1606 debugFlake << "length" << firstSegment.length() << shortenLength; 1607 qreal t = firstSegment.paramAtLength(shortenLength); 1608 firstSegments = firstSegment.splitAt(t); 1609 // transform the marker so that it goes from the first point of the first segment to the second point of the first segment 1610 QPointF startPoint = firstSegments.first.first()->point(); 1611 QPointF newStartPoint = firstSegments.first.second()->point(); 1612 QLineF vector(newStartPoint, startPoint); 1613 qreal angle = -vector.angle() + 90; 1614 QTransform transform; 1615 transform.translate(startPoint.x(), startPoint.y()) 1616 .rotate(angle) 1617 .translate(-pathBoundingRect.width() / 2.0, 0); 1618 1619 markerPath = transform.map(markerPath); 1620 QPainterPath startOutline = stroker.createStroke(markerPath); 1621 startOutline = startOutline.united(markerPath); 1622 pathOutline.addPath(startOutline); 1623 firstPoint = firstSubpath->first(); 1624 if (firstPoint->properties() & KoPathPoint::StartSubpath) { 1625 firstSegments.second.first()->setProperty(KoPathPoint::StartSubpath); 1626 } 1627 debugFlake << "start marker" << angle << startPoint << newStartPoint << firstPoint->point(); 1628 1629 if (!twoPointPath) { 1630 if (firstSegment.second()->activeControlPoint2()) { 1631 firstSegments.second.second()->setControlPoint2(firstSegment.second()->controlPoint2()); 1632 } 1633 secondPoint = (*firstSubpath)[1]; 1634 } 1635 else if (!mdEnd.marker()) { 1636 // in case it is two point path with no end marker we need to modify the last point via the secondPoint 1637 secondPoint = (*firstSubpath)[1]; 1638 } 1639 } 1640 } 1641 if (mdEnd.marker() && !closedPath) { 1642 QPainterPath markerPath = mdEnd.marker()->path(mdEnd.width(pen.widthF())); 1643 1644 KoPathSegment lastSegment; 1645 1646 /* 1647 * if the path consists only of 2 point and it it has an marker on both ends 1648 * use the firstSegments.second as that is the path that needs to be shortened 1649 */ 1650 if (twoPointPath && firstPoint) { 1651 lastSegment = firstSegments.second; 1652 } 1653 else { 1654 lastSegment = segmentByIndex(KoPathPointIndex(0, firstSubpath->count() - 2)); 1655 } 1656 1657 if (lastSegment.isValid()) { 1658 QRectF pathBoundingRect = markerPath.boundingRect(); 1659 qreal shortenLength = lastSegment.length() - pathBoundingRect.height() * shortenFactor; 1660 qreal t = lastSegment.paramAtLength(shortenLength); 1661 lastSegments = lastSegment.splitAt(t); 1662 // transform the marker so that it goes from the last point of the first segment to the previous point of the last segment 1663 QPointF startPoint = lastSegments.second.second()->point(); 1664 QPointF newStartPoint = lastSegments.second.first()->point(); 1665 QLineF vector(newStartPoint, startPoint); 1666 qreal angle = -vector.angle() + 90; 1667 QTransform transform; 1668 transform.translate(startPoint.x(), startPoint.y()).rotate(angle).translate(-pathBoundingRect.width() / 2.0, 0); 1669 1670 markerPath = transform.map(markerPath); 1671 QPainterPath endOutline = stroker.createStroke(markerPath); 1672 endOutline = endOutline.united(markerPath); 1673 pathOutline.addPath(endOutline); 1674 lastPoint = firstSubpath->last(); 1675 debugFlake << "end marker" << angle << startPoint << newStartPoint << lastPoint->point(); 1676 if (twoPointPath) { 1677 if (firstSegments.second.isValid()) { 1678 if (lastSegments.first.first()->activeControlPoint2()) { 1679 firstSegments.second.first()->setControlPoint2(lastSegments.first.first()->controlPoint2()); 1680 } 1681 } 1682 else { 1683 // if there is no start marker we need the first point needs to be changed via the preLastPoint 1684 // the flag needs to be set so the moveTo is done 1685 lastSegments.first.first()->setProperty(KoPathPoint::StartSubpath); 1686 preLastPoint = (*firstSubpath)[firstSubpath->count()-2]; 1687 } 1688 } 1689 else { 1690 if (lastSegment.first()->activeControlPoint1()) { 1691 lastSegments.first.first()->setControlPoint1(lastSegment.first()->controlPoint1()); 1692 } 1693 preLastPoint = (*firstSubpath)[firstSubpath->count()-2]; 1694 } 1695 } 1696 } 1697 1698 1699 stroker.setWidth(pen.widthF()); 1700 stroker.setJoinStyle(pen.joinStyle()); 1701 stroker.setMiterLimit(pen.miterLimit()); 1702 stroker.setCapStyle(pen.capStyle()); 1703 stroker.setDashOffset(pen.dashOffset()); 1704 stroker.setDashPattern(pen.dashPattern()); 1705 1706 // shortent the path to make it look nice 1707 // replace the point temporarily in case there is an arrow 1708 // BE AWARE: this changes the content of the path so that outline give the correct values. 1709 if (firstPoint) { 1710 firstSubpath->first() = firstSegments.second.first(); 1711 if (secondPoint) { 1712 (*firstSubpath)[1] = firstSegments.second.second(); 1713 } 1714 } 1715 if (lastPoint) { 1716 if (preLastPoint) { 1717 (*firstSubpath)[firstSubpath->count() - 2] = lastSegments.first.first(); 1718 } 1719 firstSubpath->last() = lastSegments.first.second(); 1720 } 1721 1722 QPainterPath path = stroker.createStroke(outline()); 1723 1724 if (firstPoint) { 1725 firstSubpath->first() = firstPoint; 1726 if (secondPoint) { 1727 (*firstSubpath)[1] = secondPoint; 1728 } 1729 } 1730 if (lastPoint) { 1731 if (preLastPoint) { 1732 (*firstSubpath)[firstSubpath->count() - 2] = preLastPoint; 1733 } 1734 firstSubpath->last() = lastPoint; 1735 } 1736 1737 pathOutline.addPath(path); 1738 pathOutline.setFillRule(Qt::WindingFill); 1739 1740 return pathOutline; 1741 }