You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/karbon/core/vpath.cc

1154 lines
20 KiB

/* 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 <math.h>
#include <tqdom.h>
#include <tqvaluelist.h>
#include <tqwmatrix.h>
#include "vpath.h"
#include "vsegment.h"
#include "vvisitor.h"
#include <kdebug.h>
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<VSubpathIterator*>;
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<VSubpathIterator*>::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<VSubpathIterator*>::Iterator itr = m_list->begin();
itr != m_list->end();
++itr )
{
if( ( *itr )->m_current == segment )
( *itr )->m_current = current;
}
}
}
private:
TQValueList<VSubpathIterator*>* 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<bool>( 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<double> 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<double>::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<VSegment*>( 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;
tqinvalidateBoundingBox();
return true;
}
bool
VSubpath::insert( uint index, const VSegment* segment )
{
VSegment* s = const_cast<VSegment*>( 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;
tqinvalidateBoundingBox();
return true;
}
void
VSubpath::prepend( const VSegment* segment )
{
VSegment* s = const_cast<VSegment*>( 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;
tqinvalidateBoundingBox();
}
void
VSubpath::append( const VSegment* segment )
{
VSegment* s = const_cast<VSegment*>( 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;
tqinvalidateBoundingBox();
}
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;
tqinvalidateBoundingBox();
}
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<uint>( 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<uint>( distance ) < index &&
static_cast<uint>( distance ) < m_number - index )
{
segment = m_current;
forward = index > static_cast<uint>( 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<VSubpath*>( &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();
}