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)); }
QList<KoSubpath *> KarbonSimplifyPath::split( const KoPathShape &path ) { QList<KoSubpath *> res; KoSubpath *subpath = new KoSubpath; res.append( subpath ); for ( int i = 0; i < path.pointCount(); ++i ) { KoPathPoint *p = path.pointByIndex( KoPathPointIndex(0, i) ); // if the path separates two subpaths // (if it isn't smooth nor the first or last point) if ( i != 0 && i != path.pointCount()-1 ) { KoPathPoint *prev = path.pointByIndex( KoPathPointIndex(0, i-1) ); KoPathPoint *next = path.pointByIndex( KoPathPointIndex(0, i+1) ); if ( ! p->isSmooth(prev, next) ) { // create a new subpath subpath->append( new KoPathPoint(*p) ); subpath = new KoSubpath; res.append( subpath ); } } subpath->append( new KoPathPoint(*p) ); } return res; }
void KoPathPointInsertCommand::undo() { QUndoCommand::undo(); for (int i = 0; i < m_pointDataList.size(); ++i) { const KoPathPointData &pdBefore = m_pointDataList.at(i); KoPathShape * pathShape = pdBefore.pathShape; KoPathPointIndex piAfter = pdBefore.pointIndex; ++piAfter.second; KoPathPoint * before = pathShape->pointByIndex(pdBefore.pointIndex); m_points[i] = pathShape->removePoint(piAfter); if (m_points[i]->properties() & KoPathPoint::CloseSubpath) { piAfter.second = 0; } KoPathPoint * after = pathShape->pointByIndex(piAfter); if (before->activeControlPoint2()) { QPointF controlPoint2 = before->controlPoint2(); qSwap(controlPoint2, m_controlPoints[i].first); before->setControlPoint2(controlPoint2); } if (after->activeControlPoint1()) { QPointF controlPoint1 = after->controlPoint1(); qSwap(controlPoint1, m_controlPoints[i].second); after->setControlPoint1(controlPoint1); } pathShape->update(); } m_deletePoints = true; }
KoPathSegmentBreakCommand::KoPathSegmentBreakCommand(const KoPathPointData & pointData, KUndo2Command *parent) : KUndo2Command(parent) , m_pointData(pointData) , m_startIndex(-1, -1) , m_broken(false) { if (m_pointData.pathShape->isClosedSubpath(m_pointData.pointIndex.first)) { m_startIndex = m_pointData.pointIndex; KoPathPoint * before = m_pointData.pathShape->pointByIndex(m_startIndex); if (before->properties() & KoPathPoint::CloseSubpath) { m_startIndex.second = 0; } else { ++m_startIndex.second; } } setText(QObject::tr("Break subpath")); }
KoPathControlPointMoveCommand::KoPathControlPointMoveCommand( const KoPathPointData &pointData, const QPointF &offset, KoPathPoint::PointType pointType, KUndo2Command *parent) : KUndo2Command(parent) , m_pointData(pointData) , m_pointType(pointType) { Q_ASSERT(offset.x() < 1e14 && offset.y() < 1e14); KoPathShape * pathShape = m_pointData.pathShape; KoPathPoint * point = pathShape->pointByIndex(m_pointData.pointIndex); if (point) { m_offset = point->parent()->documentToShape(offset) - point->parent()->documentToShape(QPointF(0, 0)); } setText(i18nc("(qtundo-format)", "Move control point")); }
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()); }
KoPathPointInsertCommand::KoPathPointInsertCommand(const QList<KoPathPointData> & pointDataList, qreal insertPosition, QUndoCommand *parent) : QUndoCommand(parent) , m_deletePoints(true) { if (insertPosition < 0) insertPosition = 0; if (insertPosition > 1) insertPosition = 1; //TODO the list needs to be sorted QList<KoPathPointData>::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathShape * pathShape = it->pathShape; KoPathSegment segment = pathShape->segmentByIndex(it->pointIndex); // should not happen but to be sure if (! segment.isValid()) continue; m_pointDataList.append(*it); QPair<KoPathSegment, KoPathSegment> splitSegments = segment.splitAt( insertPosition ); KoPathPoint * split1 = splitSegments.first.second(); KoPathPoint * split2 = splitSegments.second.first(); KoPathPoint * splitPoint = new KoPathPoint( pathShape, split1->point() ); if( split1->activeControlPoint1() ) splitPoint->setControlPoint1(split1->controlPoint1()); if( split2->activeControlPoint2() ) splitPoint->setControlPoint2(split2->controlPoint2()); m_points.append(splitPoint); QPointF cp1 = splitSegments.first.first()->controlPoint2(); QPointF cp2 = splitSegments.second.second()->controlPoint1(); m_controlPoints.append(QPair<QPointF, QPointF>(cp1, cp2)); } }
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 KoCreatePathTool::mouseReleaseEvent(KoPointerEvent *event) { Q_D(KoCreatePathTool); if (! d->shape || (event->buttons() & Qt::RightButton)) return; d->listeningToModifiers = true; // After the first press-and-release d->repaintActivePoint(); d->pointIsDragged = false; KoPathPoint *lastActivePoint = d->activePoint; if (!d->finishAfterThisPoint) { d->activePoint = d->shape->lineTo(event->point); canvas()->snapGuide()->setIgnoredPathPoints((QList<KoPathPoint*>()<<d->activePoint)); } // apply symmetric point property if applicable if (lastActivePoint->activeControlPoint1() && lastActivePoint->activeControlPoint2()) { QPointF diff1 = lastActivePoint->point() - lastActivePoint->controlPoint1(); QPointF diff2 = lastActivePoint->controlPoint2() - lastActivePoint->point(); if (qFuzzyCompare(diff1.x(), diff2.x()) && qFuzzyCompare(diff1.y(), diff2.y())) lastActivePoint->setProperty(KoPathPoint::IsSymmetric); } if (d->finishAfterThisPoint) { d->firstPoint->setControlPoint1(d->activePoint->controlPoint1()); delete d->shape->removePoint(d->shape->pathPointIndex(d->activePoint)); d->activePoint = d->firstPoint; d->shape->closeMerge(); // we are closing the path, so reset the existing start path point d->existingStartPoint = 0; // finish path endPath(); } if (d->angleSnapStrategy && lastActivePoint->activeControlPoint2()) { d->angleSnapStrategy->deactivate(); } }
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 KarbonSimplifyPath::removeDuplicates(KoPathShape *path) { // NOTE: works because path has only has one subshape, if this ever moves in // KoPathPoint it should be changed for (int i = 1; i < path->pointCount(); ++i) { KoPathPoint *p = path->pointByIndex(KoPathPointIndex(0, i)); KoPathPoint *prev = path->pointByIndex(KoPathPointIndex(0, i - 1)); QPointF diff = p->point() - prev->point(); // if diff = 0 remove point if (qFuzzyCompare(diff.x() + 1, 1) && qFuzzyCompare(diff.y() + 1, 1)) { if (prev->activeControlPoint1()) p->setControlPoint1(prev->controlPoint1()); else p->removeControlPoint1(); delete path->removePoint(KoPathPointIndex(0, i - 1)); --i; } } }
bool KoPathPointTypeCommand::appendPointData(KoPathPointData data) { KoPathPoint *point = data.pathShape->pointByIndex(data.pointIndex); if (! point) return false; PointData pointData(data); pointData.m_oldControlPoint1 = data.pathShape->shapeToDocument(point->controlPoint1()); pointData.m_oldControlPoint2 = data.pathShape->shapeToDocument(point->controlPoint2()); pointData.m_oldProperties = point->properties(); pointData.m_hadControlPoint1 = point->activeControlPoint1(); pointData.m_hadControlPoint2 = point->activeControlPoint2(); m_additionalPointData.append(pointData); return true; }
void KoPathPointTypeCommand::undoChanges(const QList<PointData> &data) { QList<PointData>::const_iterator it(data.begin()); for (; it != data.end(); ++it) { KoPathShape *pathShape = it->m_pointData.pathShape; KoPathPoint *point = pathShape->pointByIndex(it->m_pointData.pointIndex); point->setProperties(it->m_oldProperties); if (it->m_hadControlPoint1) point->setControlPoint1(pathShape->documentToShape(it->m_oldControlPoint1)); else point->removeControlPoint1(); if (it->m_hadControlPoint2) point->setControlPoint2(pathShape->documentToShape(it->m_oldControlPoint2)); else point->removeControlPoint2(); } }
KoPathPointTypeCommand::KoPathPointTypeCommand( const QList<KoPathPointData> & pointDataList, PointType pointType, KUndo2Command *parent) : KoPathBaseCommand(parent) , m_pointType(pointType) { QList<KoPathPointData>::const_iterator it(pointDataList.begin()); for (; it != pointDataList.end(); ++it) { KoPathPoint *point = it->pathShape->pointByIndex(it->pointIndex); if (point) { PointData pointData(*it); pointData.m_oldControlPoint1 = it->pathShape->shapeToDocument(point->controlPoint1()); pointData.m_oldControlPoint2 = it->pathShape->shapeToDocument(point->controlPoint2()); pointData.m_oldProperties = point->properties(); pointData.m_hadControlPoint1 = point->activeControlPoint1(); pointData.m_hadControlPoint2 = point->activeControlPoint2(); m_oldPointData.append(pointData); m_shapes.insert(it->pathShape); } } setText(QObject::tr("Set point type")); }
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 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 ); } } }
void TestSnapStrategy::testExtensionDirection() { /* TEST CASE 0 Supposed to return null */ ExtensionSnapStrategy toTestOne; KoPathShape uninitiatedPathShape; KoPathPoint::PointProperties normal = KoPathPoint::Normal; const QPointF initiatedPoint0(0,0); KoPathPoint initiatedPoint(&uninitiatedPathShape, initiatedPoint0, normal); QMatrix initiatedMatrixParam(1,1,1,1,1,1); const QTransform initiatedMatrix(initiatedMatrixParam); QPointF direction2 = toTestOne.extensionDirection( &initiatedPoint, initiatedMatrix); QVERIFY(direction2.isNull()); /* TEST CASE 1 tests a point that: - is the first in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has no previous point = expected returning an empty QPointF */ ExtensionSnapStrategy toTestTwo; QPointF expectedPointTwo(0,0); KoPathShape shapeOne; QPointF firstPoint(0,1); QPointF secondPoint(1,2); QPointF thirdPoint(2,3); QPointF fourthPoint(3,4); shapeOne.moveTo(firstPoint); shapeOne.lineTo(secondPoint); shapeOne.lineTo(thirdPoint); shapeOne.lineTo(fourthPoint); QPointF paramPositionTwo(0,1); KoPathPoint paramPointTwo; paramPointTwo.setPoint(paramPositionTwo); paramPointTwo.setParent(&shapeOne); const QTransform paramTransMatrix(1,2,3,4,5,6); QPointF directionTwo = toTestTwo.extensionDirection( ¶mPointTwo, paramTransMatrix); QCOMPARE(directionTwo, expectedPointTwo); /* TEST CASE 2 tests a point that: - is the second in a subpath, - does not have the firstSubpath property set, - it has no activeControlPoint1, - is has a previous point = expected returning an */ ExtensionSnapStrategy toTestThree; QPointF expectedPointThree(0,0); QPointF paramPositionThree(1,1); KoPathPoint paramPointThree; paramPointThree.setPoint(paramPositionThree); paramPointThree.setParent(&shapeOne); QPointF directionThree = toTestThree.extensionDirection( ¶mPointThree, paramTransMatrix); QCOMPARE(directionThree, expectedPointThree); }
bool KoEnhancedPathCommand::execute() { /* * The parameters of the commands are in viewbox coordinates, which have * to be converted to the shapes coordinate system by calling viewboxToShape * on the enhanced path the command works on. * Parameters which resemble angles are angles corresponding to the viewbox * coordinate system. Those have to be transformed into angles corresponding * to the normal mathematically coordinate system to be used for the arcTo * drawing routine. This is done by computing (2*M_PI - angle). */ QList<QPointF> points = pointsFromParameters(); uint pointsCount = points.size(); switch( m_command.toAscii() ) { // starts new subpath at given position (x y) + case 'M': if( ! pointsCount ) return false; m_parent->moveTo( m_parent->viewboxToShape( points[0] ) ); if( pointsCount > 1 ) for( uint i = 1; i < pointsCount; i++ ) m_parent->lineTo( m_parent->viewboxToShape( points[i] ) ); break; // line from current point (x y) + case 'L': foreach( const QPointF &point, points ) m_parent->lineTo( m_parent->viewboxToShape( point ) ); break; // cubic bezier curve from current point (x1 y1 x2 y2 x y) + case 'C': for( uint i = 0; i < pointsCount; i+=3 ) m_parent->curveTo( m_parent->viewboxToShape( points[i] ), m_parent->viewboxToShape( points[i+1] ), m_parent->viewboxToShape( points[i+2] ) ); break; // closes the current subpath case 'Z': m_parent->close(); break; // ends the current set of subpaths case 'N': // N just ends the complete path break; // no fill for current set of subpaths case 'F': // TODO implement me break; // no stroke for current set of subpaths case 'S': // TODO implement me break; // segment of an ellipse (x y w h t0 t1) + case 'T': // same like T but with implied movement to starting point (x y w h t0 t1) + case 'U': { bool lineTo = m_command == 'T'; for( uint i = 0; i < pointsCount; i+=3 ) { const QPointF &radii = m_parent->viewboxToShape( points[i+1] ); const QPointF &angles = points[i+2] / rad2deg; // compute the ellipses starting point QPointF start( radii.x() * cos( angles.x() ), radii.y() * sin( angles.x() ) ); qreal sweepAngle = degSweepAngle( points[i+2].x(), points[i+2].y(), false ); if( lineTo ) m_parent->lineTo( m_parent->viewboxToShape( points[i] ) + start ); else m_parent->moveTo( m_parent->viewboxToShape( points[i] ) + start ); m_parent->arcTo( radii.x(), radii.y(), points[i+2].x(), sweepAngle ); } } break; // counter-clockwise arc (x1 y1 x2 y2 x3 y3 x y) + case 'A': // the same as A, with implied moveto to the starting point (x1 y1 x2 y2 x3 y3 x y) + case 'B': { bool lineTo = m_command == 'A'; for( uint i = 0; i < pointsCount; i+=4 ) { QRectF bbox = rectFromPoints( points[i], points[i+1] ); QPointF center = bbox.center(); qreal rx = 0.5 * m_parent->viewboxToShape( bbox.width() ); qreal ry = 0.5 * m_parent->viewboxToShape( bbox.height() ); qreal startAngle = angleFromPoint( points[i+2] - center ); qreal stopAngle = angleFromPoint( points[i+3] - center ); // we are moving counter-clockwise to the end angle qreal sweepAngle = radSweepAngle( startAngle, stopAngle, false ); // compute the starting point to draw the line to QPointF startPoint( rx * cos( startAngle ), ry * sin( 2*M_PI - startAngle ) ); if( lineTo ) m_parent->moveTo( m_parent->viewboxToShape( center ) + startPoint ); else m_parent->moveTo( m_parent->viewboxToShape( center ) + startPoint ); m_parent->arcTo( rx, ry, startAngle * rad2deg, sweepAngle * rad2deg ); } } break; // clockwise arc (x1 y1 x2 y2 x3 y3 x y) + case 'W': // the same as W, but implied moveto (x1 y1 x2 y2 x3 y3 x y) + case 'V': { bool lineTo = m_command == 'W'; for( uint i = 0; i < pointsCount; i+=4 ) { QRectF bbox = rectFromPoints( points[i], points[i+1] ); QPointF center = bbox.center(); qreal rx = 0.5 * m_parent->viewboxToShape( bbox.width() ); qreal ry = 0.5 * m_parent->viewboxToShape( bbox.height() ); qreal startAngle = angleFromPoint( points[i+2] - center ); qreal stopAngle = angleFromPoint( points[i+3] - center ); // we are moving clockwise to the end angle qreal sweepAngle = radSweepAngle( startAngle, stopAngle, true ); if( lineTo ) m_parent->lineTo( m_parent->viewboxToShape( points[i+2] ) ); else m_parent->lineTo( m_parent->viewboxToShape( points[i+2] ) ); m_parent->arcTo( rx, ry, startAngle * rad2deg, sweepAngle * rad2deg ); } } break; // elliptical quadrant (initial segment tangential to x-axis) (x y) + case 'X': { KoPathPoint * lastPoint = lastPathPoint(); foreach( QPointF point, points ) { point = m_parent->viewboxToShape( point ); qreal rx = point.x() - lastPoint->point().x(); qreal ry = point.y() - lastPoint->point().y(); qreal startAngle = ry > 0.0 ? 90.0 : 270.0; qreal sweepAngle = rx*ry < 0.0 ? 90.0 : -90.0; lastPoint = m_parent->arcTo( fabs(rx), fabs(ry), startAngle, sweepAngle ); } } break; // elliptical quadrant (initial segment tangential to y-axis) (x y) + case 'Y': { KoPathPoint * lastPoint = lastPathPoint(); foreach( QPointF point, points ) { point = m_parent->viewboxToShape( point ); qreal rx = point.x() - lastPoint->point().x(); qreal ry = point.y() - lastPoint->point().y(); qreal startAngle = rx < 0.0 ? 0.0 : 180.0; qreal sweepAngle = rx*ry > 0.0 ? 90.0 : -90.0; lastPoint = m_parent->arcTo( fabs(rx), fabs(ry), startAngle, sweepAngle ); } }
void KoPathControlPointMoveCommand::redo() { KUndo2Command::redo(); KoPathShape * pathShape = m_pointData.pathShape; KoPathPoint * point = pathShape->pointByIndex(m_pointData.pointIndex); if (point) { pathShape->update(); if (m_pointType == KoPathPoint::ControlPoint1) { point->setControlPoint1(point->controlPoint1() + m_offset); if (point->properties() & KoPathPoint::IsSymmetric) { // set the other control point so that it lies on the line between the moved // control point and the point, with the same distance to the point as the moved point point->setControlPoint2(2.0 * point->point() - point->controlPoint1()); } else if (point->properties() & KoPathPoint::IsSmooth) { // move the other control point so that it lies on the line through point and control point // keeping its distance to the point QPointF direction = point->point() - point->controlPoint1(); direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y()); QPointF distance = point->point() - point->controlPoint2(); qreal length = sqrt(distance.x() * distance.x() + distance.y() * distance.y()); point->setControlPoint2(point->point() + length * direction); } } else if (m_pointType == KoPathPoint::ControlPoint2) { point->setControlPoint2(point->controlPoint2() + m_offset); if (point->properties() & KoPathPoint::IsSymmetric) { // set the other control point so that it lies on the line between the moved // control point and the point, with the same distance to the point as the moved point point->setControlPoint1(2.0 * point->point() - point->controlPoint2()); } else if (point->properties() & KoPathPoint::IsSmooth) { // move the other control point so that it lies on the line through point and control point // keeping its distance to the point QPointF direction = point->point() - point->controlPoint2(); direction /= sqrt(direction.x() * direction.x() + direction.y() * direction.y()); QPointF distance = point->point() - point->controlPoint1(); qreal length = sqrt(distance.x() * distance.x() + distance.y() * distance.y()); point->setControlPoint1(point->point() + length * direction); } } pathShape->normalize(); pathShape->update(); } }
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); }
bool EnhancedPathCommand::execute() { /* * The parameters of the commands are in viewbox coordinates, which have * to be converted to the shapes coordinate system by calling viewboxToShape * on the enhanced path the command works on. * Parameters which resemble angles are angles corresponding to the viewbox * coordinate system. Those have to be transformed into angles corresponding * to the normal mathematically coordinate system to be used for the arcTo * drawing routine. This is done by computing (2*M_PI - angle). */ QList<QPointF> points = pointsFromParameters(); const int pointsCount = points.size(); switch (m_command.unicode()) { // starts new subpath at given position (x y) + case 'M': if (!pointsCount) return false; m_parent->moveTo(points[0]); if (pointsCount > 1) for (int i = 1; i < pointsCount; i++) m_parent->lineTo(points[i]); break; // line from current point (x y) + case 'L': foreach(const QPointF &point, points) m_parent->lineTo(point); break; // cubic bezier curve from current point (x1 y1 x2 y2 x y) + case 'C': for (int i = 0; i < pointsCount; i+=3) m_parent->curveTo(points[i], points[i+1], points[i+2]); break; // closes the current subpath case 'Z': m_parent->close(); break; // ends the current set of subpaths case 'N': // N just ends the complete path break; // no fill for current set of subpaths case 'F': // TODO implement me break; // no stroke for current set of subpaths case 'S': // TODO implement me break; // segment of an ellipse (x y w h t0 t1) + case 'T': // same like T but with implied movement to starting point (x y w h t0 t1) + case 'U': { bool lineTo = m_command.unicode() == 'T'; for (int i = 0; i < pointsCount; i+=3) { const QPointF &radii = points[i+1]; const QPointF &angles = points[i+2] / rad2deg; // compute the ellipses starting point QPointF start(radii.x() * cos(angles.x()), -1 * radii.y() * sin(angles.x())); qreal sweepAngle = degSweepAngle(points[i+2].x(), points[i+2].y(), false); if (lineTo) m_parent->lineTo(points[i] + start); else m_parent->moveTo(points[i] + start); m_parent->arcTo(radii.x(), radii.y(), points[i+2].x(), sweepAngle); } break; } // counter-clockwise arc (x1 y1 x2 y2 x3 y3 x y) + case 'A': // the same as A, with implied moveto to the starting point (x1 y1 x2 y2 x3 y3 x y) + case 'B': // clockwise arc (x1 y1 x2 y2 x3 y3 x y) + case 'W': // the same as W, but implied moveto (x1 y1 x2 y2 x3 y3 x y) + case 'V': { bool lineTo = ((m_command.unicode() == 'A') || (m_command.unicode() == 'W')); bool clockwise = ((m_command.unicode() == 'W') || (m_command.unicode() == 'V')); for (int i = 0; i < pointsCount; i+=4) { QRectF bbox = rectFromPoints(points[i], points[i+1]); QPointF center = bbox.center(); qreal rx = 0.5 * bbox.width(); qreal ry = 0.5 * bbox.height(); if (rx == 0) { rx = 1; } if (ry == 0) { ry = 1; } QPointF startRadialVector = points[i+2] - center; QPointF endRadialVector = points[i+3] - center; // convert from ellipse space to unit-circle space qreal x0 = startRadialVector.x() / rx; qreal y0 = startRadialVector.y() / ry; qreal x1 = endRadialVector.x() / rx; qreal y1 = endRadialVector.y() / ry; qreal startAngle = angleFromPoint(QPointF(x0,y0)); qreal stopAngle = angleFromPoint(QPointF(x1,y1)); // we are moving counter-clockwise to the end angle qreal sweepAngle = radSweepAngle(startAngle, stopAngle, clockwise); // compute the starting point to draw the line to // as the point x3 y3 is not on the ellipse, spec says the point define radial vector QPointF startPoint(rx * cos(startAngle), ry * sin(2*M_PI - startAngle)); // if A or W is first command in enhanced path // move to the starting point bool isFirstCommandInPath = (m_parent->subpathCount() == 0); bool isFirstCommandInSubpath = m_parent->isClosedSubpath( m_parent->subpathCount() - 1 ); if (lineTo && !isFirstCommandInPath && !isFirstCommandInSubpath) { m_parent->lineTo(center + startPoint); } else { m_parent->moveTo(center + startPoint); } m_parent->arcTo(rx, ry, startAngle * rad2deg, sweepAngle * rad2deg); } break; } // elliptical quadrant (initial segment tangential to x-axis) (x y) + case 'X': { KoPathPoint * lastPoint = lastPathPoint(); bool xDir = true; foreach (const QPointF &point, points) { qreal rx = point.x() - lastPoint->point().x(); qreal ry = point.y() - lastPoint->point().y(); qreal startAngle = xDir ? (ry > 0.0 ? 90.0 : 270.0) : (rx < 0.0 ? 0.0 : 180.0); qreal sweepAngle = xDir ? (rx*ry < 0.0 ? 90.0 : -90.0) : (rx*ry > 0.0 ? 90.0 : -90.0); lastPoint = m_parent->arcTo(fabs(rx), fabs(ry), startAngle, sweepAngle); xDir = !xDir; } break; } // elliptical quadrant (initial segment tangential to y-axis) (x y) + case 'Y': { KoPathPoint * lastPoint = lastPathPoint(); bool xDir = false; foreach (const QPointF &point, points) { qreal rx = point.x() - lastPoint->point().x(); qreal ry = point.y() - lastPoint->point().y(); qreal startAngle = xDir ? (ry > 0.0 ? 90.0 : 270.0) : (rx < 0.0 ? 0.0 : 180.0); qreal sweepAngle = xDir ? (rx*ry < 0.0 ? 90.0 : -90.0) : (rx*ry > 0.0 ? 90.0 : -90.0); lastPoint = m_parent->arcTo(fabs(rx), fabs(ry), startAngle, sweepAngle); xDir = !xDir; } break; }