diff options
Diffstat (limited to 'karbon/core/vcomposite.cc')
-rw-r--r-- | karbon/core/vcomposite.cc | 775 |
1 files changed, 775 insertions, 0 deletions
diff --git a/karbon/core/vcomposite.cc b/karbon/core/vcomposite.cc new file mode 100644 index 00000000..8cd61311 --- /dev/null +++ b/karbon/core/vcomposite.cc @@ -0,0 +1,775 @@ +/* This file is part of the KDE project + Copyright (C) 2001, 2002, 2003 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 <qdom.h> +#include <qpainter.h> +#include <qwmatrix.h> +#include <qregexp.h> + +#include <KoPoint.h> +#include <KoRect.h> +#include <KoUnit.h> +#include <KoStore.h> +#include <KoXmlWriter.h> +#include <KoXmlNS.h> +#include <KoGenStyles.h> + +#include "vcomposite.h" +#include "vcomposite_iface.h" +#include "vfill.h" +#include "vpainter.h" +#include "vsegment.h" +#include "vstroke.h" +#include "vvisitor.h" +#include "vpath.h" +#include "commands/vtransformcmd.h" +#include "vdocument.h" + +#include <kdebug.h> + + +VPath::VPath( VObject* parent, VState state ) + : VObject( parent, state ), m_fillRule( winding ) +{ + m_paths.setAutoDelete( true ); + + // add an initial path: + m_paths.append( new VSubpath( this ) ); + + // we need a stroke for boundingBox() at anytime: + m_stroke = new VStroke( this ); + m_fill = new VFill(); + + m_drawCenterNode = false; +} + +VPath::VPath( const VPath& composite ) + : VObject( composite ), SVGPathParser() +{ + m_paths.setAutoDelete( true ); + + VSubpath* path; + + VSubpathListIterator itr( composite.m_paths ); + for( itr.toFirst(); itr.current(); ++itr ) + { + path = itr.current()->clone(); + path->setParent( this ); + m_paths.append( path ); + } + + if ( composite.stroke() ) + setStroke( *composite.stroke() ); + + if ( composite.fill() ) + setFill( *composite.fill() ); + + m_drawCenterNode = false; + m_fillRule = composite.m_fillRule; + m_matrix = composite.m_matrix; +} + +VPath::~VPath() +{ +} + +DCOPObject* VPath::dcopObject() +{ + if ( !m_dcop ) + m_dcop = new VPathIface( this ); + + return m_dcop; +} + + +void +VPath::draw( VPainter* painter, const KoRect *rect ) const +{ + if( + state() == deleted || + state() == hidden || + state() == hidden_locked ) + { + return; + } + + if( rect && !rect->intersects( boundingBox() ) ) + return; + + painter->save(); + + VSubpathListIterator itr( m_paths ); + + // draw simplistic contour: + if( state() == edit ) + { + for( itr.toFirst(); itr.current(); ++itr ) + { + if( !itr.current()->isEmpty() ) + { + painter->newPath(); + painter->setRasterOp( Qt::XorROP ); + painter->setPen( Qt::yellow ); + painter->setBrush( Qt::NoBrush ); + + VSubpathIterator jtr( *( itr.current() ) ); + for( ; jtr.current(); ++jtr ) + { + jtr.current()->draw( painter ); + } + + painter->strokePath(); + } + } + } + else if( state() != edit ) + { + // paint fill: + painter->newPath(); + painter->setFillRule( m_fillRule ); + + for( itr.toFirst(); itr.current(); ++itr ) + { + if( !itr.current()->isEmpty() ) + { + VSubpathIterator jtr( *( itr.current() ) ); + for( ; jtr.current(); ++jtr ) + { + jtr.current()->draw( painter ); + } + } + } + + painter->setRasterOp( Qt::CopyROP ); + painter->setPen( Qt::NoPen ); + painter->setBrush( *fill() ); + painter->fillPath(); + + // draw stroke: + painter->setPen( *stroke() ); + painter->setBrush( Qt::NoBrush ); + painter->strokePath(); + } + + painter->restore(); +} + +const KoPoint& +VPath::currentPoint() const +{ + return m_paths.getLast()->currentPoint(); +} + +bool +VPath::moveTo( const KoPoint& p ) +{ + // Append a new subpath if current subpath is not empty. + if( !m_paths.getLast()->isEmpty() ) + { + VSubpath* path = new VSubpath( this ); + m_paths.append( path ); + } + + return m_paths.getLast()->moveTo( p ); +} + +bool +VPath::lineTo( const KoPoint& p ) +{ + return m_paths.getLast()->lineTo( p ); +} + +bool +VPath::curveTo( + const KoPoint& p1, const KoPoint& p2, const KoPoint& p3 ) +{ + return m_paths.getLast()->curveTo( p1, p2, p3 ); +} + +bool +VPath::curve1To( const KoPoint& p2, const KoPoint& p3 ) +{ + return m_paths.getLast()->curve1To( p2, p3 ); +} + +bool +VPath::curve2To( const KoPoint& p1, const KoPoint& p3 ) +{ + return m_paths.getLast()->curve2To( p1, p3 ); +} + +bool +VPath::arcTo( const KoPoint& p1, const KoPoint& p2, const double r ) +{ + return m_paths.getLast()->arcTo( p1, p2, r ); +} + +void +VPath::close() +{ + m_paths.getLast()->close(); + + // Append a new subpath. + VSubpath* path = new VSubpath( this ); + path->moveTo( currentPoint() ); + m_paths.append( path ); +} + +bool +VPath::isClosed() const +{ + return m_paths.getLast()->isEmpty() || m_paths.getLast()->isClosed(); +} + +void +VPath::combine( const VPath& composite ) +{ + VSubpathListIterator itr( composite.m_paths ); + for( ; itr.current(); ++itr ) + { + combinePath( *( itr.current() ) ); + } +} + +void +VPath::combinePath( const VSubpath& path ) +{ + VSubpath* p = path.clone(); + p->setParent( this ); + + // TODO: do complex inside tests instead: + // Make new segments clock wise oriented: + + m_paths.append( p ); + m_fillRule = fillMode(); +} + +bool +VPath::pointIsInside( const KoPoint& p ) const +{ + // Check if point is inside boundingbox. + if( !boundingBox().contains( p ) ) + return false; + + + VSubpathListIterator itr( m_paths ); + + for( itr.toFirst(); itr.current(); ++itr ) + { + if( itr.current()->pointIsInside( p ) ) + return true; + } + + return false; +} + +bool +VPath::intersects( const VSegment& segment ) const +{ + // Check if boundingboxes intersect. + if( !boundingBox().intersects( segment.boundingBox() ) ) + return false; + + + VSubpathListIterator itr( m_paths ); + + for( itr.toFirst(); itr.current(); ++itr ) + { + if( itr.current()->intersects( segment ) ) + return true; + } + + return false; +} + + +VFillRule +VPath::fillMode() const +{ + return ( m_paths.count() > 1 ) ? evenOdd : winding; +} + +const KoRect& +VPath::boundingBox() const +{ + if( m_boundingBoxIsInvalid ) + { + VSubpathListIterator itr( m_paths ); + itr.toFirst(); + + m_boundingBox = itr.current() ? itr.current()->boundingBox() : KoRect(); + + for( ++itr; itr.current(); ++itr ) + m_boundingBox |= itr.current()->boundingBox(); + + if( !m_boundingBox.isNull() ) + { + // take line width into account: + m_boundingBox.setCoords( + m_boundingBox.left() - 0.5 * stroke()->lineWidth(), + m_boundingBox.top() - 0.5 * stroke()->lineWidth(), + m_boundingBox.right() + 0.5 * stroke()->lineWidth(), + m_boundingBox.bottom() + 0.5 * stroke()->lineWidth() ); + } + m_boundingBoxIsInvalid = false; + } + + return m_boundingBox; +} + +VPath* +VPath::clone() const +{ + return new VPath( *this ); +} + +void +VPath::save( QDomElement& element ) const +{ + if( state() != deleted ) + { + QDomElement me = element.ownerDocument().createElement( "PATH" ); + element.appendChild( me ); + + VObject::save( me ); + + QString d; + saveSvgPath( d ); + me.setAttribute( "d", d ); + + //writeTransform( me ); + + // save fill rule if necessary: + if( !( m_fillRule == evenOdd ) ) + me.setAttribute( "fillRule", m_fillRule ); + } +} + +void +VPath::saveOasis( KoStore *store, KoXmlWriter *docWriter, KoGenStyles &mainStyles, int &index ) const +{ + if( state() != deleted ) + { + docWriter->startElement( "draw:path" ); + + QString d; + saveSvgPath( d ); + docWriter->addAttribute( "svg:d", d ); + + double x = boundingBox().x(); + double y = boundingBox().y(); + double w = boundingBox().width(); + double h = boundingBox().height(); + + docWriter->addAttribute( "svg:viewBox", QString( "%1 %2 %3 %4" ).arg( x ).arg( y ).arg( w ).arg( h ) ); + docWriter->addAttributePt( "svg:x", x ); + docWriter->addAttributePt( "svg:y", y ); + docWriter->addAttributePt( "svg:width", w ); + docWriter->addAttributePt( "svg:height", h ); + + VObject::saveOasis( store, docWriter, mainStyles, index ); + + QWMatrix tmpMat; + tmpMat.scale( 1, -1 ); + tmpMat.translate( 0, -document()->height() ); + + QString transform = buildOasisTransform( tmpMat ); + if( !transform.isEmpty() ) + docWriter->addAttribute( "draw:transform", transform ); + + docWriter->endElement(); + } +} + +void +VPath::saveOasisFill( KoGenStyles &mainStyles, KoGenStyle &stylesobjectauto ) const +{ + if( m_fill ) + { + QWMatrix mat; + mat.scale( 1, -1 ); + mat.translate( 0, -document()->height() ); + + // mirror fill before saving + VFill fill( *m_fill ); + fill.transform( mat ); + fill.saveOasis( mainStyles, stylesobjectauto ); + // save fill rule if necessary: + if( !( m_fillRule == evenOdd ) ) + stylesobjectauto.addProperty( "svg:fill-rule", "winding" ); + } +} + +void +VPath::transformByViewbox( const QDomElement &element, QString viewbox ) +{ + if( ! viewbox.isEmpty() ) + { + // allow for viewbox def with ',' or whitespace + QStringList points = QStringList::split( ' ', viewbox.replace( ',', ' ' ).simplifyWhiteSpace() ); + + double w = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "width", QString::null ) ); + double h = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "height", QString::null ) ); + double x = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "x", QString::null ) ); + double y = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "y", QString::null ) ); + + QWMatrix mat; + mat.translate( x-KoUnit::parseValue( points[0] ), y-KoUnit::parseValue( points[1] ) ); + mat.scale( w / KoUnit::parseValue( points[2] ) , h / KoUnit::parseValue( points[3] ) ); + VTransformCmd cmd( 0L, mat ); + cmd.visitVPath( *this ); + } +} + +bool +VPath::loadOasis( const QDomElement &element, KoOasisLoadingContext &context ) +{ + setState( normal ); + + QString viewbox; + + if( element.localName() == "path" ) + { + QString data = element.attributeNS( KoXmlNS::svg, "d", QString::null ); + if( data.length() > 0 ) + { + loadSvgPath( data ); + } + + m_fillRule = element.attributeNS( KoXmlNS::svg, "fill-rule", QString::null ) == "winding" ? winding : evenOdd; + + viewbox = element.attributeNS( KoXmlNS::svg, "viewBox", QString::null ); + } + else if( element.localName() == "custom-shape" ) + { + QDomNodeList list = element.childNodes(); + for( uint i = 0; i < list.count(); ++i ) + { + if( list.item( i ).isElement() ) + { + QDomElement e = list.item( i ).toElement(); + if( e.namespaceURI() != KoXmlNS::draw ) + continue; + + if( e.localName() == "enhanced-geometry" ) + { + QString data = e.attributeNS( KoXmlNS::draw, "enhanced-path", QString::null ); + if( ! data.isEmpty() ) + loadSvgPath( data ); + + viewbox = e.attributeNS( KoXmlNS::svg, "viewBox", QString::null ); + } + } + } + } + + transformByViewbox( element, viewbox ); + + QString trafo = element.attributeNS( KoXmlNS::draw, "transform", QString::null ); + if( !trafo.isEmpty() ) + transformOasis( trafo ); + + return VObject::loadOasis( element, context ); +} + +void +VPath::load( const QDomElement& element ) +{ + setState( normal ); + + VObject::load( element ); + + QString data = element.attribute( "d" ); + if( data.length() > 0 ) + { + loadSvgPath( data ); + } + m_fillRule = element.attribute( "fillRule" ) == 0 ? evenOdd : winding; + QDomNodeList list = element.childNodes(); + for( uint i = 0; i < list.count(); ++i ) + { + if( list.item( i ).isElement() ) + { + QDomElement child = list.item( i ).toElement(); + + if( child.tagName() == "PATH" ) + { + VSubpath path( this ); + path.load( child ); + + combinePath( path ); + } + else + { + VObject::load( child ); + } + } + } + + QString trafo = element.attribute( "transform" ); + if( !trafo.isEmpty() ) + transform( trafo ); +} + +void +VPath::loadSvgPath( const QString &d ) +{ + //QTime s;s.start(); + parseSVG( d, true ); + //kdDebug(38000) << "Parsing time : " << s.elapsed() << endl; +} + +void +VPath::saveSvgPath( QString &d ) const +{ + // save paths to svg: + VSubpathListIterator itr( m_paths ); + for( itr.toFirst(); itr.current(); ++itr ) + { + if( !itr.current()->isEmpty() ) + itr.current()->saveSvgPath( d ); + } +} + +void +VPath::svgMoveTo( double x1, double y1, bool ) +{ + moveTo( KoPoint( x1, y1 ) ); +} + +void +VPath::svgLineTo( double x1, double y1, bool ) +{ + lineTo( KoPoint( x1, y1 ) ); +} + +void +VPath::svgCurveToCubic( double x1, double y1, double x2, double y2, double x, double y, bool ) +{ + curveTo( KoPoint( x1, y1 ), KoPoint( x2, y2 ), KoPoint( x, y ) ); +} + +void +VPath::svgClosePath() +{ + close(); +} + +void +VPath::accept( VVisitor& visitor ) +{ + visitor.visitVPath( *this ); +} + +void +VPath::transform( const QString &transform ) +{ + VTransformCmd cmd( 0L, parseTransform( transform ) ); + cmd.visitVPath( *this ); +} + +void +VPath::transformOasis( const QString &transform ) +{ + VTransformCmd cmd( 0L, parseOasisTransform( transform ) ); + cmd.visitVPath( *this ); +} + +QWMatrix +VPath::parseTransform( const QString &transform ) +{ + QWMatrix result; + + // Split string for handling 1 transform statement at a time + QStringList subtransforms = QStringList::split(')', transform); + QStringList::ConstIterator it = subtransforms.begin(); + QStringList::ConstIterator end = subtransforms.end(); + for(; it != end; ++it) + { + QStringList subtransform = QStringList::split('(', (*it)); + + subtransform[0] = subtransform[0].stripWhiteSpace().lower(); + subtransform[1] = subtransform[1].simplifyWhiteSpace(); + QRegExp reg("[,( ]"); + QStringList params = QStringList::split(reg, subtransform[1]); + + if(subtransform[0].startsWith(";") || subtransform[0].startsWith(",")) + subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); + + if(subtransform[0] == "rotate") + { + if(params.count() == 3) + { + double x = params[1].toDouble(); + double y = params[2].toDouble(); + + result.translate(x, y); + result.rotate(params[0].toDouble()); + result.translate(-x, -y); + } + else + result.rotate(params[0].toDouble()); + } + else if(subtransform[0] == "translate") + { + if(params.count() == 2) + result.translate(params[0].toDouble(), params[1].toDouble()); + else // Spec : if only one param given, assume 2nd param to be 0 + result.translate(params[0].toDouble() , 0); + } + else if(subtransform[0] == "scale") + { + if(params.count() == 2) + result.scale(params[0].toDouble(), params[1].toDouble()); + else // Spec : if only one param given, assume uniform scaling + result.scale(params[0].toDouble(), params[0].toDouble()); + } + else if(subtransform[0] == "skewx") + result.shear(tan(params[0].toDouble() * VGlobal::pi_180), 0.0F); + else if(subtransform[0] == "skewy") + result.shear(tan(params[0].toDouble() * VGlobal::pi_180), 0.0F); + else if(subtransform[0] == "skewy") + result.shear(0.0F, tan(params[0].toDouble() * VGlobal::pi_180)); + else if(subtransform[0] == "matrix") + { + if(params.count() >= 6) + result.setMatrix(params[0].toDouble(), params[1].toDouble(), params[2].toDouble(), params[3].toDouble(), params[4].toDouble(), params[5].toDouble()); + } + } + + return result; +} + +QWMatrix +VPath::parseOasisTransform( const QString &transform ) +{ + QWMatrix result; + + // Split string for handling 1 transform statement at a time + QStringList subtransforms = QStringList::split(')', transform); + QStringList::ConstIterator it = subtransforms.begin(); + QStringList::ConstIterator end = subtransforms.end(); + for(; it != end; ++it) + { + QStringList subtransform = QStringList::split('(', (*it)); + + subtransform[0] = subtransform[0].stripWhiteSpace().lower(); + subtransform[1] = subtransform[1].simplifyWhiteSpace(); + QRegExp reg("[,( ]"); + QStringList params = QStringList::split(reg, subtransform[1]); + + if(subtransform[0].startsWith(";") || subtransform[0].startsWith(",")) + subtransform[0] = subtransform[0].right(subtransform[0].length() - 1); + + if(subtransform[0] == "rotate") + { + // TODO find out what oo2 really does when rotating, it seems severly broken + if(params.count() == 3) + { + double x = KoUnit::parseValue( params[1] ); + double y = KoUnit::parseValue( params[2] ); + + result.translate(x, y); + // oo2 rotates by radians + result.rotate( params[0].toDouble()*VGlobal::one_pi_180 ); + result.translate(-x, -y); + } + else + { + // oo2 rotates by radians + result.rotate( params[0].toDouble()*VGlobal::one_pi_180 ); + } + } + else if(subtransform[0] == "translate") + { + if(params.count() == 2) + { + double x = KoUnit::parseValue( params[0] ); + double y = KoUnit::parseValue( params[1] ); + result.translate(x, y); + } + else // Spec : if only one param given, assume 2nd param to be 0 + result.translate( KoUnit::parseValue( params[0] ) , 0); + } + else if(subtransform[0] == "scale") + { + if(params.count() == 2) + result.scale(params[0].toDouble(), params[1].toDouble()); + else // Spec : if only one param given, assume uniform scaling + result.scale(params[0].toDouble(), params[0].toDouble()); + } + else if(subtransform[0] == "skewx") + result.shear(tan(params[0].toDouble()), 0.0F); + else if(subtransform[0] == "skewy") + result.shear(tan(params[0].toDouble()), 0.0F); + else if(subtransform[0] == "skewy") + result.shear(0.0F, tan(params[0].toDouble())); + else if(subtransform[0] == "matrix") + { + if(params.count() >= 6) + result.setMatrix(params[0].toDouble(), params[1].toDouble(), params[2].toDouble(), params[3].toDouble(), KoUnit::parseValue( params[4] ), KoUnit::parseValue( params[5] ) ); + } + } + + return result; +} + +QString +VPath::buildSvgTransform() const +{ + return buildSvgTransform( m_matrix ); +} + +QString +VPath::buildSvgTransform( const QWMatrix &mat ) const +{ + QString transform; + if( !mat.isIdentity() ) + { + transform = QString( "matrix(%1, %2, %3, %4, %5, %6)" ).arg( mat.m11() ) + .arg( mat.m12() ) + .arg( mat.m21() ) + .arg( mat.m22() ) + .arg( mat.dx() ) + .arg( mat.dy() ); + } + return transform; +} + +QString +VPath::buildOasisTransform() const +{ + return buildSvgTransform( m_matrix ); +} + +QString +VPath::buildOasisTransform( const QWMatrix &mat ) const +{ + QString transform; + if( !mat.isIdentity() ) + { + transform = QString( "matrix(%1, %2, %3, %4, %5pt, %6pt)" ).arg( mat.m11() ) + .arg( mat.m12() ) + .arg( mat.m21() ) + .arg( mat.m22() ) + .arg( mat.dx() ) + .arg( mat.dy() ); + } + return transform; +} |