/* 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 #include #include #include #include #include #include #include #include #include #include #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 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( TQt::XorROP ); painter->setPen( TQt::yellow ); painter->setBrush( TQt::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( TQt::CopyROP ); painter->setPen( TQt::NoPen ); painter->setBrush( *fill() ); painter->fillPath(); // draw stroke: painter->setPen( *stroke() ); painter->setBrush( TQt::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( TQDomElement& element ) const { if( state() != deleted ) { TQDomElement me = element.ownerDocument().createElement( "PATH" ); element.appendChild( me ); VObject::save( me ); TQString 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" ); TQString 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", TQString( "%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 ); TQWMatrix tmpMat; tmpMat.scale( 1, -1 ); tmpMat.translate( 0, -document()->height() ); TQString transform = buildOasisTransform( tmpMat ); if( !transform.isEmpty() ) docWriter->addAttribute( "draw:transform", transform ); docWriter->endElement(); } } void VPath::saveOasisFill( KoGenStyles &mainStyles, KoGenStyle &stylesobjectauto ) const { if( m_fill ) { TQWMatrix 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 TQDomElement &element, TQString viewbox ) { if( ! viewbox.isEmpty() ) { // allow for viewbox def with ',' or whitespace TQStringList points = TQStringList::split( ' ', viewbox.replace( ',', ' ' ).simplifyWhiteSpace() ); double w = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "width", TQString() ) ); double h = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "height", TQString() ) ); double x = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "x", TQString() ) ); double y = KoUnit::parseValue( element.attributeNS( KoXmlNS::svg, "y", TQString() ) ); TQWMatrix 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 TQDomElement &element, KoOasisLoadingContext &context ) { setState( normal ); TQString viewbox; if( element.localName() == "path" ) { TQString data = element.attributeNS( KoXmlNS::svg, "d", TQString() ); if( data.length() > 0 ) { loadSvgPath( data ); } m_fillRule = element.attributeNS( KoXmlNS::svg, "fill-rule", TQString() ) == "winding" ? winding : evenOdd; viewbox = element.attributeNS( KoXmlNS::svg, "viewBox", TQString() ); } else if( element.localName() == "custom-shape" ) { TQDomNodeList list = element.childNodes(); for( uint i = 0; i < list.count(); ++i ) { if( list.item( i ).isElement() ) { TQDomElement e = list.item( i ).toElement(); if( e.namespaceURI() != KoXmlNS::draw ) continue; if( e.localName() == "enhanced-geometry" ) { TQString data = e.attributeNS( KoXmlNS::draw, "enhanced-path", TQString() ); if( ! data.isEmpty() ) loadSvgPath( data ); viewbox = e.attributeNS( KoXmlNS::svg, "viewBox", TQString() ); } } } } transformByViewbox( element, viewbox ); TQString trafo = element.attributeNS( KoXmlNS::draw, "transform", TQString() ); if( !trafo.isEmpty() ) transformOasis( trafo ); return VObject::loadOasis( element, context ); } void VPath::load( const TQDomElement& element ) { setState( normal ); VObject::load( element ); TQString data = element.attribute( "d" ); if( data.length() > 0 ) { loadSvgPath( data ); } m_fillRule = element.attribute( "fillRule" ) == 0 ? evenOdd : winding; TQDomNodeList list = element.childNodes(); for( uint i = 0; i < list.count(); ++i ) { if( list.item( i ).isElement() ) { TQDomElement child = list.item( i ).toElement(); if( child.tagName() == "PATH" ) { VSubpath path( this ); path.load( child ); combinePath( path ); } else { VObject::load( child ); } } } TQString trafo = element.attribute( "transform" ); if( !trafo.isEmpty() ) transform( trafo ); } void VPath::loadSvgPath( const TQString &d ) { //TQTime s;s.start(); parseSVG( d, true ); //kdDebug(38000) << "Parsing time : " << s.elapsed() << endl; } void VPath::saveSvgPath( TQString &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 TQString &transform ) { VTransformCmd cmd( 0L, parseTransform( transform ) ); cmd.visitVPath( *this ); } void VPath::transformOasis( const TQString &transform ) { VTransformCmd cmd( 0L, parseOasisTransform( transform ) ); cmd.visitVPath( *this ); } TQWMatrix VPath::parseTransform( const TQString &transform ) { TQWMatrix result; // Split string for handling 1 transform statement at a time TQStringList subtransforms = TQStringList::split(')', transform); TQStringList::ConstIterator it = subtransforms.begin(); TQStringList::ConstIterator end = subtransforms.end(); for(; it != end; ++it) { TQStringList subtransform = TQStringList::split('(', (*it)); subtransform[0] = subtransform[0].stripWhiteSpace().lower(); subtransform[1] = subtransform[1].simplifyWhiteSpace(); TQRegExp reg("[,( ]"); TQStringList params = TQStringList::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; } TQWMatrix VPath::parseOasisTransform( const TQString &transform ) { TQWMatrix result; // Split string for handling 1 transform statement at a time TQStringList subtransforms = TQStringList::split(')', transform); TQStringList::ConstIterator it = subtransforms.begin(); TQStringList::ConstIterator end = subtransforms.end(); for(; it != end; ++it) { TQStringList subtransform = TQStringList::split('(', (*it)); subtransform[0] = subtransform[0].stripWhiteSpace().lower(); subtransform[1] = subtransform[1].simplifyWhiteSpace(); TQRegExp reg("[,( ]"); TQStringList params = TQStringList::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; } TQString VPath::buildSvgTransform() const { return buildSvgTransform( m_matrix ); } TQString VPath::buildSvgTransform( const TQWMatrix &mat ) const { TQString transform; if( !mat.isIdentity() ) { transform = TQString( "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; } TQString VPath::buildOasisTransform() const { return buildSvgTransform( m_matrix ); } TQString VPath::buildOasisTransform( const TQWMatrix &mat ) const { TQString transform; if( !mat.isIdentity() ) { transform = TQString( "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; }