/* This file is part of the KDE project Copyright (C) 2002, The Karbon Developers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "vpath.h" #include "vsegment.h" #include "vvisitor.h" #include class VSubpathIteratorList { public: VSubpathIteratorList() : m_list( 0L ), m_iterator( 0L ) {} ~VSubpathIteratorList() { notifyClear( true ); delete m_list; } void add( VSubpathIterator* itr ) { if( !m_iterator ) m_iterator = itr; else if( m_list ) m_list->push_front( itr ); else { m_list = new TQValueList; m_list->push_front( itr ); } } void remove( VSubpathIterator* itr ) { if( m_iterator == itr ) m_iterator = 0L; else if( m_list ) { m_list->remove( itr ); if( m_list->isEmpty() ) { delete m_list; m_list = 0L; } } } void notifyClear( bool zeroList ) { if( m_iterator ) { if( zeroList ) m_iterator->m_list = 0L; m_iterator->m_current = 0L; } if( m_list ) { for( TQValueList::Iterator itr = m_list->begin(); itr != m_list->end(); ++itr ) { if( zeroList ) ( *itr )->m_list = 0L; ( *itr )->m_current = 0L; } } } void notifyRemove( VSegment* segment, VSegment* current ) { if( m_iterator ) { if( m_iterator->m_current == segment ) m_iterator->m_current = current; } if( m_list ) { for( TQValueList::Iterator itr = m_list->begin(); itr != m_list->end(); ++itr ) { if( ( *itr )->m_current == segment ) ( *itr )->m_current = current; } } } private: TQValueList* m_list; VSubpathIterator* m_iterator; }; VSubpath::VSubpath( VObject* tqparent ) : VObject( tqparent ) { m_isClosed = false; m_first = m_last = m_current = 0L; m_number = 0; m_currentIndex = -1; m_iteratorList = 0L; // Add an initial segment. append( new VSegment( 1 ) ); } VSubpath::VSubpath( const VSubpath& list ) : VObject( list ) { m_isClosed = list.isClosed(); m_first = m_last = m_current = 0L; m_number = 0; m_currentIndex = -1; m_iteratorList = 0L; VSegment* segment = list.m_first; while( segment ) { append( segment->clone() ); segment = segment->m_next; } } VSubpath::VSubpath( const VSegment& segment ) : VObject( 0L ) { m_isClosed = false; m_first = m_last = m_current = 0L; m_number = 0; m_currentIndex = -1; m_iteratorList = 0L; // The segment is not a "begin" segment. if( segment.prev() ) { // Add an initial segment. append( new VSegment( 1 ) ); // Move the "begin" segment to the new segment's previous knot. moveTo( segment.prev()->knot() ); } // Append a copy of the segment. append( segment.clone() ); } VSubpath::~VSubpath() { clear(); delete m_iteratorList; } const KoPoint& VSubpath::currentPoint() const { return getLast()->knot(); } bool VSubpath::moveTo( const KoPoint& p ) { // Move "begin" segment if path is still empty. if( isEmpty() ) { getLast()->setKnot( p ); return true; } return false; } bool VSubpath::lineTo( const KoPoint& p ) { if( isClosed() ) return false; VSegment* s = new VSegment( 1 ); s->setDegree( 1 ); s->setKnot( p ); append( s ); return true; } bool VSubpath::curveTo( const KoPoint& p1, const KoPoint& p2, const KoPoint& p3 ) { if( isClosed() ) return false; VSegment* s = new VSegment(); s->setDegree( 3 ); s->setPoint( 0, p1 ); s->setPoint( 1, p2 ); s->setPoint( 2, p3 ); append( s ); return true; } bool VSubpath::curve1To( const KoPoint& p2, const KoPoint& p3 ) { if( isClosed() ) return false; VSegment* s = new VSegment(); s->setDegree( 3 ); s->setPoint( 0, currentPoint() ); s->setPoint( 1, p2 ); s->setPoint( 2, p3 ); append( s ); return true; } bool VSubpath::curve2To( const KoPoint& p1, const KoPoint& p3 ) { if( isClosed() ) return false; VSegment* s = new VSegment(); s->setDegree( 3 ); s->setPoint( 0, p1 ); s->setPoint( 1, p3 ); s->setPoint( 2, p3 ); append( s ); return true; } bool VSubpath::arcTo( const KoPoint& p1, const KoPoint& p2, const double r ) { /* This routine is inspired by code in GNU ghostscript. * * |- P1B3 -| * * |- - - T12- - -| * * - - P1 x....__--o.....x P2 * | | : _/ B3 * P1B0 : / * | :/ * | | * - T10 o B0 * | * | | * | * | | * - x P0 */ if( isClosed() || r < 0.0 ) return false; // We need to calculate the tangent points. Therefore calculate tangents // T10=P1P0 and T12=P1P2 first. double dx0 = currentPoint().x() - p1.x(); double dy0 = currentPoint().y() - p1.y(); double dx2 = p2.x() - p1.x(); double dy2 = p2.y() - p1.y(); // Calculate distance squares. double dsqT10 = dx0 * dx0 + dy0 * dy0; double dsqT12 = dx2 * dx2 + dy2 * dy2; // We now calculate tan(a/2) where a is the angle between T10 and T12. // We benefit from the facts T10*T12 = |T10|*|T12|*cos(a), // |T10xT12| = |T10|*|T12|*sin(a) (cross product) and tan(a/2) = sin(a)/[1-cos(a)]. double num = dy0 * dx2 - dy2 * dx0; double denom = sqrt( dsqT10 * dsqT12 ) - ( dx0 * dx2 + dy0 * dy2 ); // The points are colinear. if( 1.0 + denom == 1.0 ) { // Just add a line. lineTo( p1 ); } else { // |P1B0| = |P1B3| = r * tan(a/2). double dP1B0 = fabs( r * num / denom ); // B0 = P1 + |P1B0| * T10/|T10|. KoPoint b0 = p1 + KoPoint( dx0, dy0 ) * ( dP1B0 / sqrt( dsqT10 ) ); // If B0 deviates from current point P0, add a line to it. if( !b0.isNear( currentPoint(), VGlobal::isNearRange ) ) lineTo( b0 ); // B3 = P1 + |P1B3| * T12/|T12|. KoPoint b3 = p1 + KoPoint( dx2, dy2 ) * ( dP1B0 / sqrt( dsqT12 ) ); // The two bezier-control points are located on the tangents at a fraction // of the distance[ tangent points <-> tangent intersection ]. const KoPoint d = p1 - b0; double distsq = d * d; double rsq = r * r; double fract; // r is very small. if( distsq >= rsq * VGlobal::veryBigNumber ) { // Assume dist = r = 0. fract = 0.0; } else { fract = ( 4.0 / 3.0 ) / ( 1.0 + sqrt( 1.0 + distsq / rsq ) ); } KoPoint b1 = b0 + ( p1 - b0 ) * fract; KoPoint b2 = b3 + ( p1 - b3 ) * fract; // Finally add the bezier-segment. curveTo( b1, b2, b3 ); } return true; } void VSubpath::close() { // In the case the list is 100% empty (which should actually never happen), // append a "begin" first, to avoid a crash. if( count() == 0 ) append( new VSegment( 1 ) ); // Move last segment if we are already closed. if( isClosed() ) { getLast()->setKnot( getFirst()->knot() ); } // Append a line, if necessary. else { if( getLast()->knot().isNear( getFirst()->knot(), VGlobal::isNearRange ) ) { // Move last knot. getLast()->setKnot( getFirst()->knot() ); } else { // Add a line. lineTo( getFirst()->knot() ); } m_isClosed = true; } } bool VSubpath::pointIsInside( const KoPoint& p ) const { // If the point is not inside the boundingbox, it cannot be inside the path either. if( !boundingBox().tqcontains( p ) ) return false; // First check if the point is inside the knot polygon (beziers are treated // as lines). /* This algorithm is taken from "Fast Winding Number Inclusion of a Point * in a Polygon" by Dan Sunday, tqgeometryalgorithms.com. */ /* int windingNumber = 0; // Ommit first segment. VSegment* segment = getFirst()->next(); while( segment ) { if( segment->prev()->knot().y() <= p.y() ) { // Upward crossing. if( segment->knot().y() > p.y() ) { // Point is left. if( segment->pointIsLeft( p ) > 0 ) { // Valid up intersection. ++windingNumber; } } } else { // Downward crossing. if( segment->knot().y() <= p.y() ) { // Point is right. if( segment->pointIsLeft( p ) < 0 ) { // Valid down intersection. --windingNumber; } } } segment = segment->next(); } if( static_cast( windingNumber ) ) return true; */ // Then check if the point is located in between the knot polygon // and the bezier curves. /* We rotate each segment in order to make their chord (the line between * the previous knot and the knot ) parallel to the x-axis. Then we * calculate y(xp) on the segment for the rotated input point (xp,yp) * and compare y(xp) with yp. */ // TODO // cache the closed evaluation bool closed = isClosed() || getLast()->knot() == getFirst()->knot(); TQValueList rparams; VSegment* segment = getFirst()->next(); // move all segements so that p is the origin // and compute their intersections with the x-axis while( segment ) { VSubpath tmpCurve( 0L ); tmpCurve.append( new VSegment( segment->degree() ) ); for( int i = 0; i <= segment->degree(); ++i ) tmpCurve.current()->setP(i, segment->p(i)-p ); tmpCurve.current()->rootParams( rparams ); segment = segment->next(); } // if the path is not closed, compute the intersection of // the line through the first and last knot and the x-axis too if( ! closed ) { KoPoint prevKnot = getLast()->knot() - p; KoPoint nextKnot = getFirst()->knot() - p; double dx = nextKnot.x() - prevKnot.x(); double dy = nextKnot.y() - prevKnot.y(); if( dx == 0.0 ) { rparams.append( nextKnot.x() ); } else if( dy != 0.0 ) { if( ( prevKnot.y() < 0.0 && nextKnot.y() > 0.0 ) || ( prevKnot.y() > 0.0 && nextKnot.y() < 0.0 ) ) { double n = prevKnot.y() - dy / dx * prevKnot.x(); rparams.append( -n * dx / dy ); } } } kdDebug(38000) << "intersection count: " << rparams.count() << endl; // sort all intersections qHeapSort( rparams ); TQValueList::iterator itr, etr = rparams.end(); for( itr = rparams.begin(); itr != etr; ++itr ) kdDebug(38000) << "intersection: " << *itr << endl; if( closed ) { // pair the intersections and check if the origin is within a pair for( itr = rparams.begin(); itr != etr; ++itr ) { if( *itr > 0.0 ) return false; if( ++itr == etr ) return false; if( *itr > 0.0 ) return true; } } else { // only check if point is between first and last intersection if we have an open path if ( rparams.front() < 0.0 && rparams.back() > 0.0 ) return true; } return false; } bool VSubpath::intersects( const VSegment& s ) const { // Check if path is empty and if boundingboxes intersect. if( isEmpty() || !boundingBox().intersects( s.boundingBox() ) ) { return false; } // Ommit first segment. VSegment* segment = getFirst()->next(); while( segment ) { if( segment->intersects( s ) ) { return true; } segment = segment->next(); } return false; } bool VSubpath::counterClockwise() const { /* This algorithm is taken from the FAQ of comp.graphics.algorithms: * "Find the lowest vertex (or, if there is more than one vertex with the * same lowest coordinate, the rightmost of those vertices) and then take * the cross product of the edges fore and aft of it." */ // A non closed path does not have a winding. if( !isClosed() ) { return false; } VSegment* segment = getFirst(); // We save the segment not the knot itself. Initialize it with the // first segment: const VSegment* bottomRight = getFirst(); while( segment ) { if( segment->knot().y() < bottomRight->knot().y() ) bottomRight = segment; else if( segment->knot().y() - bottomRight->knot().y() < VGlobal::isNearRange ) { if( segment->knot().x() > bottomRight->knot().x() ) bottomRight = segment; } segment = segment->next(); } // Catch boundary case (bottomRight is first or last segment): const VSegment* current; const VSegment* next; if( bottomRight == getFirst() ) current = getLast(); else current = bottomRight; if( bottomRight == getLast() ) next = getFirst()->next(); else next = bottomRight->next(); // Check "z-component" of cross product: return ( next->knot().x() - next->prev()->knot().x() ) * ( current->knot().y() - current->prev()->knot().y() ) - ( next->knot().y() - next->prev()->knot().y() ) * ( current->knot().x() - current->prev()->knot().x() ) < 0.0; } void VSubpath::revert() { // Catch case where the list is "empty". if( isEmpty() ) return; VSubpath list( tqparent() ); list.moveTo( getLast()->knot() ); VSegment* segment = getLast(); while( segment->prev() ) { list.append( segment->revert() ); segment = segment->prev(); } list.m_isClosed = isClosed(); *this = list; } const KoRect& VSubpath::boundingBox() const { if( m_boundingBoxIsInvalid ) { // Reset the boundingbox. m_boundingBox = KoRect(); VSegment* segment = m_first; while( segment ) { if( segment->state() != VSegment::deleted ) m_boundingBox |= segment->boundingBox(); segment = segment->m_next; } m_boundingBoxIsInvalid = false; } return m_boundingBox; } VSubpath* VSubpath::clone() const { return new VSubpath( *this ); } void VSubpath::saveSvgPath( TQString &d ) const { // Save segments. VSegment* segment = getFirst(); while( segment ) { if( segment->state() == VSegment::normal ) { if( segment->degree() <= 2 ) { // Line. if( segment->prev() ) { d += TQString( "L%1 %2" ). tqarg( segment->knot().x() ).tqarg( segment->knot().y() ); } // Moveto. else { d += TQString( "M%1 %2" ). tqarg( segment->knot().x() ).tqarg( segment->knot().y() ); } } // Bezier ( degree >= 3 ). else { // We currently treat all beziers as cubic beziers. d += TQString( "C%1 %2 %3 %4 %5 %6" ). arg( segment->point( segment->degree() - 3 ).x() ). arg( segment->point( segment->degree() - 3 ).y() ). arg( segment->point( segment->degree() - 2 ).x() ). arg( segment->point( segment->degree() - 2 ).y() ). arg( segment->knot().x() ). arg( segment->knot().y() ); } } segment = segment->m_next; } if( isClosed() ) d += "Z"; } // TODO: remove this backward compatibility function after koffice 1.3.x void VSubpath::load( const TQDomElement& element ) { // We might have a "begin" segment. clear(); TQDomNodeList list = element.childNodes(); for( uint i = 0; i < list.count(); ++i ) { if( list.item( i ).isElement() ) { TQDomElement segment = list.item( i ).toElement(); VSegment* s = new VSegment(); s->load( segment ); append( s ); } } if( element.attribute( "isClosed" ) == 0 ? false : true ) close(); } void VSubpath::accept( VVisitor& visitor ) { visitor.visitVSubpath( *this ); } VSubpath& VSubpath::operator=( const VSubpath& list ) { if( this == &list ) return *this; m_isClosed = list.isClosed(); clear(); VSegment* segment = list.m_first; while( segment ) { append( segment->clone() ); segment = segment->m_next; } m_current = m_first; m_currentIndex = 0; return *this; } bool VSubpath::insert( const VSegment* segment ) { if( m_currentIndex == -1 ) return false; VSegment* s = const_cast( segment ); VSegment* prev = m_current->m_prev; m_current->m_prev = s; prev->m_next = s; s->m_prev = prev; s->m_next = m_current; m_current = s; ++m_number; invalidateBoundingBox(); return true; } bool VSubpath::insert( uint index, const VSegment* segment ) { VSegment* s = const_cast( segment ); if( index == 0 ) { prepend( s ); return true; } else if( index == m_number ) { append( s ); return true; } VSegment* next = locate( index ); if( !next ) return false; VSegment* prev = next->m_prev; next->m_prev = s; prev->m_next = s; s->m_prev = prev; s->m_next = next; m_current = s; ++m_number; invalidateBoundingBox(); return true; } void VSubpath::prepend( const VSegment* segment ) { VSegment* s = const_cast( segment ); s->m_prev = 0L; if( ( s->m_next = m_first ) ) m_first->m_prev = s; else m_last = s; m_first = m_current = s; ++m_number; m_currentIndex = 0; invalidateBoundingBox(); } void VSubpath::append( const VSegment* segment ) { VSegment* s = const_cast( segment ); s->m_next = 0L; if( ( s->m_prev = m_last ) ) m_last->m_next = s; else m_first = s; m_last = m_current = s; m_currentIndex = m_number; ++m_number; invalidateBoundingBox(); } void VSubpath::clear() { VSegment* segment = m_first; m_first = m_last = m_current = 0L; m_number = 0; m_currentIndex = -1; if( m_iteratorList ) m_iteratorList->notifyClear( false ); VSegment* prev; while( segment ) { prev = segment; segment = segment->m_next; delete prev; } m_isClosed = false; invalidateBoundingBox(); } VSegment* VSubpath::first() { if( m_first ) { m_currentIndex = 0; return m_current = m_first; } return 0L; } VSegment* VSubpath::last() { if( m_last ) { m_currentIndex = m_number - 1; return m_current = m_last; } return 0L; } VSegment* VSubpath::prev() { if( m_current ) { if( m_current->m_prev ) { --m_currentIndex; return m_current = m_current->m_prev; } m_currentIndex = -1; m_current = 0L; } return 0L; } VSegment* VSubpath::next() { if( m_current ) { if( m_current->m_next ) { ++m_currentIndex; return m_current = m_current->m_next; } m_currentIndex = -1; m_current = 0L; } return 0L; } VSegment* VSubpath::locate( uint index ) { if( index == static_cast( m_currentIndex ) ) return m_current; if( !m_current && m_first ) { m_current = m_first; m_currentIndex = 0; } VSegment* segment; int distance = index - m_currentIndex; bool forward; if( index >= m_number ) return 0L; if( distance < 0 ) distance = -distance; if( static_cast( distance ) < index && static_cast( distance ) < m_number - index ) { segment = m_current; forward = index > static_cast( m_currentIndex ); } else if( index < m_number - index ) { segment = m_first; distance = index; forward = true; } else { segment = m_last; distance = m_number - index - 1; if( distance < 0 ) distance = 0; forward = false; } if( forward ) { while( distance-- ) segment = segment->m_next; } else { while( distance-- ) segment = segment->m_prev; } m_currentIndex = index; return m_current = segment; } VSubpathIterator::VSubpathIterator( const VSubpath& list ) { m_list = const_cast( &list ); m_current = m_list->m_first; if( !m_list->m_iteratorList ) m_list->m_iteratorList = new VSubpathIteratorList(); m_list->m_iteratorList->add( this ); } VSubpathIterator::VSubpathIterator( const VSubpathIterator& itr ) { m_list = itr.m_list; m_current = itr.m_current; if( m_list ) m_list->m_iteratorList->add( this ); } VSubpathIterator::~VSubpathIterator() { if( m_list ) m_list->m_iteratorList->remove( this ); } VSubpathIterator& VSubpathIterator::operator=( const VSubpathIterator& itr ) { if( m_list ) m_list->m_iteratorList->remove( this ); m_list = itr.m_list; m_current = itr.m_current; if( m_list ) m_list->m_iteratorList->add( this ); return *this; } VSegment* VSubpathIterator::current() const { // If m_current points to a deleted segment, find the next not // deleted segment. if( m_current && m_current->state() == VSegment::deleted ) { return m_current->next(); } return m_current; } VSegment* VSubpathIterator::operator()() { if( VSegment* const old = current() ) { m_current = current()->next(); return old; } return 0L; } VSegment* VSubpathIterator::operator++() { if( current() ) return m_current = current()->next(); return 0L; } VSegment* VSubpathIterator::operator+=( uint i ) { while( current() && i-- ) m_current = current()->next(); return current(); } VSegment* VSubpathIterator::operator--() { if( current() ) return m_current = current()->prev(); return 0L; } VSegment* VSubpathIterator::operator-=( uint i ) { while( current() && i-- ) m_current = current()->prev(); return current(); }