void TestSegmentTypeCommand::changeToLine() { KoPathShape path; path.moveTo( QPointF(0,0) ); path.curveTo( QPointF(25,25), QPointF(75,25), QPointF(100,0) ); KoPathPointData segment(&path, KoPathPointIndex(0,0)); QList<KoPathPointData> segments; segments.append(segment); // get first segment KoPathSegment s = path.segmentByIndex(KoPathPointIndex(0,0)); KoPathSegmentTypeCommand cmd(segments, KoPathSegmentTypeCommand::Line); QVERIFY(s.first()->activeControlPoint2()); QVERIFY(s.second()->activeControlPoint1()); cmd.redo(); QVERIFY(!s.first()->activeControlPoint2()); QVERIFY(!s.second()->activeControlPoint1()); cmd.undo(); QVERIFY(s.first()->activeControlPoint2()); QVERIFY(s.second()->activeControlPoint1()); }
void KarbonCalligraphicShape::appendPointsToPathAux(const QPointF &p1, const QPointF &p2) { KoPathPoint *pathPoint1 = new KoPathPoint(this, p1); KoPathPoint *pathPoint2 = new KoPathPoint(this, p2); // calculate the index of the insertion position int index = pointCount() / 2; insertPoint(pathPoint2, KoPathPointIndex(0, index)); insertPoint(pathPoint1, KoPathPointIndex(0, index)); }
bool KarbonCalligraphicShape::flipDetected(const QPointF &p1, const QPointF &p2) { // detect the flip caused by the angle changing 180 degrees // thus detect the boundary crossing int index = pointCount() / 2; QPointF last1 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF last2 = pointByIndex(KoPathPointIndex(0, index))->point(); int sum1 = std::abs(ccw(p1, p2, last1) + ccw(p1, last2, last1)); int sum2 = std::abs(ccw(p2, p1, last2) + ccw(p2, last1, last2)); // if there was a flip return sum1 < 2 && sum2 < 2; }
void KarbonCalligraphicShape::addCap(int index1, int index2, int pointIndex, bool inverted) { QPointF p1 = m_points[index1]->point(); QPointF p2 = m_points[index2]->point(); // TODO: review why spikes can appear with a lower limit QPointF delta = p2 - p1; if (delta.manhattanLength() < 1.0) return; QPointF direction = QLineF(QPointF(0, 0), delta).unitVector().p2(); qreal width = m_points[index2]->width(); QPointF p = p2 + direction * m_caps * width; KoPathPoint * newPoint = new KoPathPoint(this, p); qreal angle = m_points[index2]->angle(); if (inverted) angle += M_PI; qreal dx = std::cos(angle) * width; qreal dy = std::sin(angle) * width; newPoint->setControlPoint1(QPointF(p.x() - dx / 2, p.y() - dy / 2)); newPoint->setControlPoint2(QPointF(p.x() + dx / 2, p.y() + dy / 2)); insertPoint(newPoint, KoPathPointIndex(0, pointIndex)); }
void KarbonPathRefineCommand::redo() { // check if we have to create the insert points commands if (! d->initialized) { // create insert point commands, one for each point to insert // into each segment for (uint iteration = 0; iteration < d->insertCount; ++iteration) { // in each iteration collect the (iteration+1)th point which starts a segments // into which we insert the point of this iteration QList<KoPathPointData> pointData; // calculate the segment position where to insert the point qreal insertPosition = 1.0 / (d->insertCount + 1 - iteration); int subpathCount = d->path->subpathCount(); // iterate over the paths subpaths for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { int pointCount = d->path->subpathPointCount(subpathIndex); // iterate over the subpaths points for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { // we only collect the (iteration+1)th point if ((pointIndex + 1) % (iteration + 1) != 0) continue; pointData.append(KoPathPointData(d->path, KoPathPointIndex(subpathIndex, pointIndex))); } } // create the command and execute it KUndo2Command * cmd = new KoPathPointInsertCommand(pointData, insertPosition, this); cmd->redo(); } d->initialized = true; } else { KUndo2Command::redo(); } d->path->update(); }
/* * The algorithm to break a multiple open or closed subpaths is: * Subpath is closed * - open behind the last point in the subpath * - go on like as described in Not closed * Not closed * - break from the back of the subpath */ KoPathBreakAtPointCommand::KoPathBreakAtPointCommand(const QList<KoPathPointData> & pointDataList, KUndo2Command *parent) : KUndo2Command(parent) , m_deletePoints(true) { QList<KoPathPointData> sortedPointDataList(pointDataList); qSort(sortedPointDataList); setText(i18nc("(qtundo-format)", "Break subpath at points")); QList<KoPathPointData>::const_iterator it(sortedPointDataList.constBegin()); for (; it != sortedPointDataList.constEnd(); ++it) { KoPathShape * pathShape = it->pathShape; KoPathPoint * point = pathShape->pointByIndex(it->pointIndex); if(! point) continue; // check if subpath is closed and the point is start or end point of the subpath if(! pathShape->isClosedSubpath(it->pointIndex.first)) { if(it->pointIndex.second == 0 || it->pointIndex.second == pathShape->subpathPointCount(it->pointIndex.first)) { continue; } } m_pointDataList.append(*it); m_points.push_back(new KoPathPoint(*point)); m_closedIndex.push_back(KoPathPointIndex(-1, 0)); } KoPathPointData last(0, KoPathPointIndex(-1, -1)); for (int i = m_pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData ¤t = m_pointDataList.at(i); if (last.pathShape != current.pathShape || last.pointIndex.first != current.pointIndex.first) { last = current; if (current.pathShape->isClosedSubpath(current.pointIndex.first)) { // the break will happen before the inserted point so we have to increment by 1 m_closedIndex[i] = current.pointIndex; ++m_closedIndex[i].second; } } } }
const QRectF KarbonCalligraphicShape::lastPieceBoundingRect() { if (pointCount() < 6) return QRectF(); int index = pointCount() / 2; QPointF p1 = pointByIndex(KoPathPointIndex(0, index - 3))->point(); QPointF p2 = pointByIndex(KoPathPointIndex(0, index - 2))->point(); QPointF p3 = pointByIndex(KoPathPointIndex(0, index - 1))->point(); QPointF p4 = pointByIndex(KoPathPointIndex(0, index))->point(); QPointF p5 = pointByIndex(KoPathPointIndex(0, index + 1))->point(); QPointF p6 = pointByIndex(KoPathPointIndex(0, index + 2))->point(); // TODO: also take the control points into account QPainterPath p; p.moveTo(p1); p.lineTo(p2); p.lineTo(p3); p.lineTo(p4); p.lineTo(p5); p.lineTo(p6); return p.boundingRect().translated(position()); }
void KarbonWhirlPinchCommand::undo() { d->pathShape->update(); uint subpathCount = d->pathData.count(); for( uint subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex ) { uint pointCount = d->pathData[subpathIndex].count(); for( uint pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { KoPathPoint * p = d->pathShape->pointByIndex( KoPathPointIndex( subpathIndex, pointIndex ) ); d->pathData[subpathIndex][pointIndex].restorePoint( p ); } } d->pathShape->normalize(); d->pathShape->update(); QUndoCommand::undo(); }
KarbonWhirlPinchCommand::KarbonWhirlPinchCommand( KoPathShape * path, qreal angle, qreal pinch, qreal radius, QUndoCommand *parent ) : QUndoCommand( parent ), d( new Private( path, angle, pinch, radius ) ) { setText( i18n( "Whirl & pinch" ) ); // save the path point data used for undo uint subpathCount = d->pathShape->subpathCount(); for( uint subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex ) { QList<PointData> subpathData; int pointCount = d->pathShape->pointCountSubpath( subpathIndex ); for( int pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { KoPathPoint * p = d->pathShape->pointByIndex( KoPathPointIndex( subpathIndex, pointIndex ) ); subpathData.append( PointData( p ) ); } d->pathData.append( subpathData ); } }
void KoPathBreakAtPointCommand::redo() { KUndo2Command::redo(); KoPathPointData last(0, KoPathPointIndex(-1, -1)); // offset, needed when path was opened int offset = 0; for (int i = m_pointDataList.size() - 1; i >= 0; --i) { const KoPathPointData & pd = m_pointDataList.at(i); KoPathShape * pathShape = pd.pathShape; KoPathPointIndex pointIndex = pd.pointIndex; if (last.pathShape != pathShape || last.pointIndex.first != pointIndex.first) { offset = 0; } pointIndex.second = pointIndex.second + offset + 1; pathShape->insertPoint(m_points[i], pointIndex); if (m_closedIndex.at(i).first != -1) { m_closedIndex[i] = pathShape->openSubpath(m_closedIndex.at(i)); offset = m_closedIndex.at(i).second; } else { KoPathPointIndex breakIndex = pd.pointIndex; breakIndex.second += offset; pathShape->breakAfter(breakIndex); m_closedIndex[i].second = offset; } if (last.pathShape != pathShape) { if (last.pathShape) { last.pathShape->update(); } last = pd; } } if (last.pathShape) { last.pathShape->update(); } m_deletePoints = false; }
void RoundCornersCommand::copyPath(KoPathShape * dst, KoPathShape * src) { dst->clear(); int subpathCount = src->subpathCount(); for (int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex) { int pointCount = src->subpathPointCount(subpathIndex); if (! pointCount) continue; KoSubpath * subpath = new KoSubpath; for (int pointIndex = 0; pointIndex < pointCount; ++pointIndex) { KoPathPoint * p = src->pointByIndex(KoPathPointIndex(subpathIndex, pointIndex)); KoPathPoint * c = new KoPathPoint(*p); c->setParent(dst); subpath->append(c); } dst->addSubpath(subpath, subpathIndex); } dst->setTransformation(src->transformation()); }
KoSubpathJoinCommand::KoSubpathJoinCommand(const KoPathPointData &pointData1, const KoPathPointData &pointData2, KUndo2Command *parent) : KUndo2Command(parent) , m_pointData1(pointData1) , m_pointData2(pointData2) , m_splitIndex(KoPathPointIndex(-1, -1)) , m_oldProperties1(KoPathPoint::Normal) , m_oldProperties2(KoPathPoint::Normal) , m_reverse(0) { Q_ASSERT(m_pointData1.pathShape == m_pointData2.pathShape); KoPathShape * pathShape = m_pointData1.pathShape; Q_ASSERT(!pathShape->isClosedSubpath(m_pointData1.pointIndex.first)); Q_ASSERT(m_pointData1.pointIndex.second == 0 || m_pointData1.pointIndex.second == pathShape->subpathPointCount(m_pointData1.pointIndex.first) - 1); Q_ASSERT(!pathShape->isClosedSubpath(m_pointData2.pointIndex.first)); Q_ASSERT(m_pointData2.pointIndex.second == 0 || m_pointData2.pointIndex.second == pathShape->subpathPointCount(m_pointData2.pointIndex.first) - 1); //TODO check that points are not the same if (m_pointData2 < m_pointData1) qSwap(m_pointData1, m_pointData2); if (m_pointData1.pointIndex.first != m_pointData2.pointIndex.first) { if (m_pointData1.pointIndex.second == 0 && pathShape->subpathPointCount(m_pointData1.pointIndex.first) > 1) m_reverse |= ReverseFirst; if (m_pointData2.pointIndex.second != 0) m_reverse |= ReverseSecond; setText(kundo2_i18n("Close subpath")); } else { setText(kundo2_i18n("Join subpaths")); } KoPathPoint * point1 = pathShape->pointByIndex(m_pointData1.pointIndex); KoPathPoint * point2 = pathShape->pointByIndex(m_pointData2.pointIndex); m_oldControlPoint1 = QPointF(pathShape->shapeToDocument(m_reverse & 1 ? point1->controlPoint1() : point1->controlPoint2())); m_oldControlPoint2 = QPointF(pathShape->shapeToDocument(m_reverse & 2 ? point2->controlPoint1() : point2->controlPoint2())); m_oldProperties1 = point1->properties(); m_oldProperties2 = point2->properties(); }
void KarbonWhirlPinchCommand::redo() { d->pathShape->update(); uint subpathCount = d->pathData.count(); for( uint subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex ) { uint pointCount = d->pathData[subpathIndex].count(); for( uint pointIndex = 0; pointIndex < pointCount; ++pointIndex ) { KoPathPoint * p = d->pathShape->pointByIndex( KoPathPointIndex( subpathIndex, pointIndex ) ); p->setPoint( d->whirlPinch( p->point() ) ); if( p->activeControlPoint1() ) p->setControlPoint1( d->whirlPinch( p->controlPoint1() ) ); if( p->activeControlPoint2() ) p->setControlPoint2( d->whirlPinch( p->controlPoint2() ) ); } } d->pathShape->normalize(); d->pathShape->update(); QUndoCommand::redo(); }
void KarbonCalligraphicShape::addCap(int index1, int index2, int pointIndex, bool inverted) { QPointF p1 = m_points[index1]->point(); QPointF p2 = m_points[index2]->point(); qreal width = m_points[index2]->width(); QPointF direction = QLineF(QPointF(0, 0), p2 - p1).unitVector().p2(); QPointF p = p2 + direction * m_caps * width; KoPathPoint * newPoint = new KoPathPoint(this, p); qreal angle = m_points[index2]->angle(); if (inverted) angle += M_PI; qreal dx = std::cos(angle) * width; qreal dy = std::sin(angle) * width; newPoint->setControlPoint1(QPointF(p.x() - dx / 2, p.y() - dy / 2)); newPoint->setControlPoint2(QPointF(p.x() + dx / 2, p.y() + dy / 2)); insertPoint(newPoint, KoPathPointIndex(0, pointIndex)); }
void KarbonCalligraphicShape:: appendPointToPath(const KarbonCalligraphicPoint &p) { qreal dx = std::cos(p.angle()) * p.width(); qreal dy = std::sin(p.angle()) * p.width(); // find the outline points QPointF p1 = p.point() - QPointF(dx / 2, dy / 2); QPointF p2 = p.point() + QPointF(dx / 2, dy / 2); if (pointCount() == 0) { moveTo(p1); lineTo(p2); normalize(); return; } // pointCount > 0 bool flip = (pointCount() >= 2) ? flipDetected(p1, p2) : false; // if there was a flip add additional points if (flip) { appendPointsToPathAux(p2, p1); if (pointCount() > 4) smoothLastPoints(); } appendPointsToPathAux(p1, p2); if (pointCount() > 4) { smoothLastPoints(); if (flip) { int index = pointCount() / 2; // find the last two points KoPathPoint *last1 = pointByIndex(KoPathPointIndex(0, index - 1)); KoPathPoint *last2 = pointByIndex(KoPathPointIndex(0, index)); last1->removeControlPoint1(); last1->removeControlPoint2(); last2->removeControlPoint1(); last2->removeControlPoint2(); m_lastWasFlip = true; } if (m_lastWasFlip) { int index = pointCount() / 2; // find the previous two points KoPathPoint *prev1 = pointByIndex(KoPathPointIndex(0, index - 2)); KoPathPoint *prev2 = pointByIndex(KoPathPointIndex(0, index + 1)); prev1->removeControlPoint1(); prev1->removeControlPoint2(); prev2->removeControlPoint1(); prev2->removeControlPoint2(); if (! flip) m_lastWasFlip = false; } } normalize(); // add initial cap if it's the fourth added point // this code is here because this function is called from different places // pointCount() == 8 may causes crashes because it doesn't take possible // flips into account if (m_points.count() >= 4 && &p == m_points[3]) { kDebug(38000) << "Adding caps!!!!!!!!!!!!!!!!" << m_points.count(); addCap(3, 0, 0, true); // duplicate the last point to make the points remain "balanced" // needed to keep all indexes code (else I would need to change // everything in the code...) KoPathPoint *last = pointByIndex(KoPathPointIndex(0, pointCount() - 1)); KoPathPoint *newPoint = new KoPathPoint(this, last->point()); insertPoint(newPoint, KoPathPointIndex(0, pointCount())); close(); } }
void KoPathPointTypeCommand::redo() { KUndo2Command::redo(); repaint(false); m_additionalPointData.clear(); QList<PointData>::iterator it(m_oldPointData.begin()); for (; it != m_oldPointData.end(); ++it) { KoPathPoint *point = it->m_pointData.pathShape->pointByIndex(it->m_pointData.pointIndex); KoPathPoint::PointProperties properties = point->properties(); switch (m_pointType) { case Line: { point->removeControlPoint1(); point->removeControlPoint2(); break; } case Curve: { KoPathPointIndex pointIndex = it->m_pointData.pointIndex; KoPathPointIndex prevIndex; KoPathPointIndex nextIndex; KoPathShape * path = it->m_pointData.pathShape; // get previous path node if (pointIndex.second > 0) prevIndex = KoPathPointIndex(pointIndex.first, pointIndex.second - 1); else if (pointIndex.second == 0 && path->isClosedSubpath(pointIndex.first)) prevIndex = KoPathPointIndex(pointIndex.first, path->subpathPointCount(pointIndex.first) - 1); // get next node if (pointIndex.second < path->subpathPointCount(pointIndex.first) - 1) nextIndex = KoPathPointIndex(pointIndex.first, pointIndex.second + 1); else if (pointIndex.second < path->subpathPointCount(pointIndex.first) - 1 && path->isClosedSubpath(pointIndex.first)) nextIndex = KoPathPointIndex(pointIndex.first, 0); KoPathPoint * prevPoint = path->pointByIndex(prevIndex); KoPathPoint * nextPoint = path->pointByIndex(nextIndex); if (prevPoint && ! point->activeControlPoint1() && appendPointData(KoPathPointData(path, prevIndex))) { KoPathSegment cubic = KoPathSegment(prevPoint, point).toCubic(); if (prevPoint->activeControlPoint2()) { prevPoint->setControlPoint2(cubic.first()->controlPoint2()); point->setControlPoint1(cubic.second()->controlPoint1()); } else point->setControlPoint1(cubic.second()->controlPoint1()); } if (nextPoint && ! point->activeControlPoint2() && appendPointData(KoPathPointData(path, nextIndex))) { KoPathSegment cubic = KoPathSegment(point, nextPoint).toCubic(); if (nextPoint->activeControlPoint1()) { point->setControlPoint2(cubic.first()->controlPoint2()); nextPoint->setControlPoint1(cubic.second()->controlPoint1()); } else point->setControlPoint2(cubic.first()->controlPoint2()); } break; } case Symmetric: { properties &= ~KoPathPoint::IsSmooth; properties |= KoPathPoint::IsSymmetric; // calculate vector from node point to first control point and normalize it QPointF directionC1 = point->controlPoint1() - point->point(); qreal dirLengthC1 = sqrt(directionC1.x() * directionC1.x() + directionC1.y() * directionC1.y()); directionC1 /= dirLengthC1; // calculate vector from node point to second control point and normalize it QPointF directionC2 = point->controlPoint2() - point->point(); qreal dirLengthC2 = sqrt(directionC2.x() * directionC2.x() + directionC2.y() * directionC2.y()); directionC2 /= dirLengthC2; // calculate the average distance of the control points to the node point qreal averageLength = 0.5 * (dirLengthC1 + dirLengthC2); // compute position of the control points so that they lie on a line going through the node point // the new distance of the control points is the average distance to the node point point->setControlPoint1(point->point() + 0.5 * averageLength * (directionC1 - directionC2)); point->setControlPoint2(point->point() + 0.5 * averageLength * (directionC2 - directionC1)); } break; case Smooth: { properties &= ~KoPathPoint::IsSymmetric; properties |= KoPathPoint::IsSmooth; // calculate vector from node point to first control point and normalize it QPointF directionC1 = point->controlPoint1() - point->point(); qreal dirLengthC1 = sqrt(directionC1.x() * directionC1.x() + directionC1.y() * directionC1.y()); directionC1 /= dirLengthC1; // calculate vector from node point to second control point and normalize it QPointF directionC2 = point->controlPoint2() - point->point(); qreal dirLengthC2 = sqrt(directionC2.x() * directionC2.x() + directionC2.y() * directionC2.y()); directionC2 /= dirLengthC2; // compute position of the control points so that they lie on a line going through the node point // the new distance of the control points is the average distance to the node point point->setControlPoint1(point->point() + 0.5 * dirLengthC1 * (directionC1 - directionC2)); point->setControlPoint2(point->point() + 0.5 * dirLengthC2 * (directionC2 - directionC1)); } break; case Corner: default: properties &= ~KoPathPoint::IsSymmetric; properties &= ~KoPathPoint::IsSmooth; break; } point->setProperties(properties); } repaint(true); }
void RoundCornersCommand::roundPath() { /* * This algorithm is worked out by <kudling AT kde DOT org> to produce similar results as * the "round corners" algorithms found in other applications. Neither code nor * algorithms from any 3rd party is used though. * * We want to replace all corners with round corners having "radius" m_radius. * The algorithm doesn't really produce circular arcs, but that's ok since * the algorithm achieves nice looking results and is generic enough to be applied * to all kind of paths. * Note also, that this algorithm doesn't touch smooth joins (in the sense of * KoPathPoint::isSmooth() ). * * We'll manipulate the input path for bookkeeping purposes and construct a new * temporary path in parallel. We finally replace the input path with the new path. * * * Without restricting generality, let's assume the input path is closed and * contains segments which build a rectangle. * * 2 * O------------O * | | Numbers reflect the segments' order * 3| |1 in the path. We neglect the "begin" * | | segment here. * O------------O * 0 * * There are three unique steps to process. The second step is processed * many times in a loop. * * 1) Begin * ----- * Split the first segment of the input path (called "path[0]" here) * at parameter t * * t = path[0]->param( m_radius ) * * and move newPath to this new knot. If current segment is too small * (smaller than 2 * m_radius), we always set t = 0.5 here and in the further * steps as well. * * path: new path: * * 2 * O------------O * | | * 3 | | 1 The current segment is marked with "#"s. * | | * O##O#########O ...O * 0 0 * * 2) Loop * ---- * The loop step is iterated over all segments. After each appliance the index n * is incremented and the loop step is reapplied until no untouched segment is left. * * Split the current segment path[n] of the input path at parameter t * * t = path[n]->param( path[n]->length() - m_radius ) * * and add the first subsegment of the curent segment to newPath. * * path: new path: * * 2 * O------------O * | | * 3 | | 1 * | | * O--O######O##O O------O... * 0 0 * * Now make the second next segment (the original path[1] segment in our example) * the current one. Split it at parameter t * * t = path[n]->param( m_radius ) * * path: new path: * * 2 * O------------O * | # * 3 | O 1 * | # * O--O------O--O O------O... * 0 0 * * Make the first subsegment of the current segment the current one. * * path: new path: * * 2 * O------------O * | | * 3 | O 1 O * | # /.1 * O--O------O--O O------O... * 0 0 * * 3) End * --- * * path: new path: * * 2 4 * O--O------O--O 5 .O------O. 3 * | | / \ * 3 O O 1 6 O O 2 * | | 7 .\ / * O--O------O--O ...O------O. 1 * 0 0 */ // TODO: not sure if we should only touch flat segment joins as the original algorithm m_path->clear(); int subpathCount = m_copy->subpathCount(); for( int subpathIndex = 0; subpathIndex < subpathCount; ++subpathIndex ) { int pointCount = m_copy->pointCountSubpath( subpathIndex ); if( ! pointCount ) continue; // check if we have sufficient number of points if( pointCount < 3 ) { // copy the only segment KoPathSegment s = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, 0 ) ); m_path->moveTo( m_copy->pointByIndex( KoPathPointIndex( subpathIndex, 0 ) )->point() ); addSegment( m_path, s ); continue; } KoPathSegment prevSeg = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, pointCount-1 ) ); KoPathSegment nextSeg = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, 0 ) ); KoPathSegment lastSeg; KoPathPoint * currPoint = nextSeg.first(); KoPathPoint * firstPoint = 0; KoPathPoint * lastPoint = 0; // check if first path point is a smooth join with the closing segment bool firstPointIsCorner = m_copy->isClosedSubpath( subpathIndex ) && ! currPoint->isSmooth( prevSeg.first(), nextSeg.second() ); // Begin: take care of the first path point if( firstPointIsCorner ) { // split the previous segment at length - radius qreal prevLength = prevSeg.length(); qreal prevSplit = prevLength > m_radius ? prevSeg.paramAtLength( prevLength-m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> prevParts = prevSeg.splitAt( prevSplit ); // split the next segment at radius qreal nextLength = nextSeg.length(); qreal nextSplit = nextLength > m_radius ? nextSeg.paramAtLength( m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> nextParts = nextSeg.splitAt( nextSplit ); // calculate smooth tangents QPointF P0 = prevParts.first.second()->point(); QPointF P3 = nextParts.first.second()->point(); qreal tangentLength1 = 0.5 * QLineF( P0, currPoint->point() ).length(); qreal tangentLength2 = 0.5 * QLineF( P3, currPoint->point() ).length(); QPointF P1 = P0 - tangentLength1 * tangentAtEnd( prevParts.first ); QPointF P2 = P3 - tangentLength2 * tangentAtStart( nextParts.second ); // start the subpath firstPoint = m_path->moveTo( prevParts.second.first()->point() ); // connect the split points with curve // TODO: shall we create a correct arc? m_path->curveTo( P1, P2, P3 ); prevSeg = nextParts.second; lastSeg = prevParts.first; } else { firstPoint = m_path->moveTo( currPoint->point() ); prevSeg = nextSeg; } // Loop: for( int pointIndex = 1; pointIndex < pointCount; ++pointIndex ) { nextSeg = m_copy->segmentByIndex( KoPathPointIndex( subpathIndex, pointIndex ) ); if( ! nextSeg.isValid() ) break; currPoint = nextSeg.first(); if( ! currPoint ) continue; if( currPoint->isSmooth( prevSeg.first(), nextSeg.second() ) ) { // the current point has a smooth join, so we can add the previous segment // to our new path addSegment( m_path, prevSeg ); prevSeg = nextSeg; } else { // split the previous segment at length - radius qreal prevLength = prevSeg.length(); qreal prevSplit = prevLength > m_radius ? prevSeg.paramAtLength( prevLength-m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> prevParts = prevSeg.splitAt( prevSplit ); // add the remaining part up to the split point of the pervious segment lastPoint = addSegment( m_path, prevParts.first ); // split the next segment at radius qreal nextLength = nextSeg.length(); qreal nextSplit = nextLength > m_radius ? nextSeg.paramAtLength( m_radius ) : 0.5; QPair<KoPathSegment,KoPathSegment> nextParts = nextSeg.splitAt( nextSplit ); // calculate smooth tangents QPointF P0 = prevParts.first.second()->point(); QPointF P3 = nextParts.first.second()->point(); qreal tangentLength1 = 0.5 * QLineF( P0, currPoint->point() ).length(); qreal tangentLength2 = 0.5 * QLineF( P3, currPoint->point() ).length(); QPointF P1 = P0 - tangentLength1 * tangentAtEnd( prevParts.first ); QPointF P2 = P3 - tangentLength2 * tangentAtStart( nextParts.second ); // connect the split points with curve // TODO: shall we create a correct arc? lastPoint = m_path->curveTo( P1, P2, P3 ); prevSeg = nextParts.second; } } // End: take care of the last path point if( firstPointIsCorner ) { // construct the closing segment lastPoint->setProperty( KoPathPoint::CloseSubpath ); firstPoint->setProperty( KoPathPoint::CloseSubpath ); switch( lastSeg.degree() ) { case 1: lastPoint->removeControlPoint2(); firstPoint->removeControlPoint1(); break; case 2: if( lastSeg.first()->activeControlPoint2() ) { lastPoint->setControlPoint2( lastSeg.first()->controlPoint2() ); firstPoint->removeControlPoint1(); } else { lastPoint->removeControlPoint2(); firstPoint->setControlPoint1( lastSeg.second()->controlPoint1() ); } break; case 3: lastPoint->setControlPoint2( lastSeg.first()->controlPoint2() ); firstPoint->setControlPoint1( lastSeg.second()->controlPoint1() ); break; } } else { // add the last remaining segment addSegment( m_path, prevSeg ); } } }