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;
}
Beispiel #3
0
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"));
}
Beispiel #6
0
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());
}
Beispiel #7
0
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));
}
Beispiel #9
0
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 );
        }
    }
}
Beispiel #17
0
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( &paramPointTwo, 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( &paramPointThree, paramTransMatrix);
    QCOMPARE(directionThree, expectedPointThree);

}
Beispiel #18
0
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;
    }