diff options
Diffstat (limited to 'filters/karbon/svg/svgimport.cpp')
-rw-r--r-- | filters/karbon/svg/svgimport.cpp | 1389 |
1 files changed, 1389 insertions, 0 deletions
diff --git a/filters/karbon/svg/svgimport.cpp b/filters/karbon/svg/svgimport.cpp new file mode 100644 index 000000000..9d323d3da --- /dev/null +++ b/filters/karbon/svg/svgimport.cpp @@ -0,0 +1,1389 @@ +/* This file is part of the KDE project + Copyright (C) 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 <svgimport.h> +#include "color.h" +#include <KoFilterChain.h> +#include <KoPageLayout.h> +#include <kgenericfactory.h> +#include <kdebug.h> +#include <KoUnit.h> +#include <KoGlobal.h> +#include <shapes/vellipse.h> +#include <shapes/vrectangle.h> +#include <shapes/vpolygon.h> +#include <commands/vtransformcmd.h> +#include <core/vsegment.h> +#include <core/vtext.h> +#include <core/vglobal.h> +#include <core/vgroup.h> +#include <core/vimage.h> +#include <core/vlayer.h> +#include <tqcolor.h> +#include <tqfile.h> +#include <tqregexp.h> +#include <kfilterdev.h> + +typedef KGenericFactory<SvgImport, KoFilter> SvgImportFactory; +K_EXPORT_COMPONENT_FACTORY( libkarbonsvgimport, SvgImportFactory( "kofficefilters" ) ) + +SvgImport::SvgImport(KoFilter *, const char *, const TQStringList&) : + KoFilter(), + outdoc( "DOC" ) +{ + m_gc.setAutoDelete( true ); +} + +SvgImport::~SvgImport() +{ +} + +KoFilter::ConversionStatus SvgImport::convert(const TQCString& from, const TQCString& to) +{ + // check for proper conversion + if( to != "application/x-karbon" || from != "image/svg+xml" ) + return KoFilter::NotImplemented; + + //Find the last extension + TQString strExt; + TQString fileIn ( m_chain->inputFile() ); + const int result=fileIn.findRev('.'); + if (result>=0) + strExt=fileIn.mid(result).lower(); + + TQString strMime; // Mime type of the compressor + if ((strExt==".gz") //in case of .svg.gz (logical extension) + ||(strExt==".svgz")) //in case of .svgz (extension used prioritary) + strMime="application/x-gzip"; // Compressed with gzip + else if (strExt==".bz2") //in case of .svg.bz2 (logical extension) + strMime="application/x-bzip2"; // Compressed with bzip2 + else + strMime="text/plain"; + + /*kdDebug(30514) << "File extension: -" << strExt << "- Compression: " << strMime << endl;*/ + + TQIODevice* in = KFilterDev::deviceForFile(fileIn,strMime); + + if (!in->open(IO_ReadOnly)) + { + kdError(30514) << "Cannot open file! Aborting!" << endl; + delete in; + return KoFilter::FileNotFound; + } + + int line, col; + TQString errormessage; + + const bool parsed=inpdoc.setContent( in, &errormessage, &line, &col ); + + in->close(); + delete in; + + if ( ! parsed ) + { + kdError(30514) << "Error while parsing file: " + << "at line " << line << " column: " << col + << " message: " << errormessage << endl; + // ### TODO: feedback to the user + return KoFilter::ParsingError; + } + + // Do the conversion! + convert(); + // add paper info, we always need custom for svg (Rob) + TQDomElement paper = outdoc.createElement( "PAPER" ); + outdoc.documentElement().appendChild( paper ); + paper.setAttribute( "format", PG_CUSTOM ); + paper.setAttribute( "width", m_document.width() ); + paper.setAttribute( "height", m_document.height() ); + + KoStoreDevice* out = m_chain->storageFile( "root", KoStore::Write ); + if( !out ) + { + kdError(30514) << "Unable to open output file!" << endl; + return KoFilter::StorageCreationError; + } + TQCString cstring = outdoc.toCString(); // utf-8 already + out->writeBlock( cstring.data(), cstring.length() ); + + return KoFilter::OK; // was successful +} + +void SvgImport::convert() +{ + SvgGraphicsContext *gc = new SvgGraphicsContext; + TQDomElement docElem = inpdoc.documentElement(); + KoRect bbox( 0, 0, 550.0, 841.0 ); + double width = !docElem.attribute( "width" ).isEmpty() ? parseUnit( docElem.attribute( "width" ), true, false, bbox ) : 550.0; + double height = !docElem.attribute( "height" ).isEmpty() ? parseUnit( docElem.attribute( "height" ), false, true, bbox ) : 841.0; + m_document.setWidth( width ); + m_document.setHeight( height ); + + m_outerRect = m_document.boundingBox(); + + // undo y-mirroring + //m_debug->append(TQString("%1\tUndo Y-mirroring.").arg(m_time.elapsed())); + if( !docElem.attribute( "viewBox" ).isEmpty() ) + { + // allow for viewbox def with ',' or whitespace + TQString viewbox( docElem.attribute( "viewBox" ) ); + TQStringList points = TQStringList::split( ' ', viewbox.replace( ',', ' ').simplifyWhiteSpace() ); + + gc->matrix.scale( width / points[2].toFloat() , height / points[3].toFloat() ); + m_outerRect.setWidth( m_outerRect.width() * ( points[2].toFloat() / width ) ); + m_outerRect.setHeight( m_outerRect.height() * ( points[3].toFloat() / height ) ); + } + + m_gc.push( gc ); + parseGroup( 0L, docElem ); + + TQWMatrix mat; + mat.scale( 1, -1 ); + mat.translate( 0, -m_document.height() ); + VTransformCmd trafo( 0L, mat ); + trafo.visit( m_document ); + outdoc = m_document.saveXML(); +} + +#define DPI 90 + +// Helper functions +// --------------------------------------------------------------------------------------- + +double SvgImport::toPercentage( TQString s ) +{ + if( s.endsWith( "%" ) ) + return s.remove( '%' ).toDouble(); + else + return s.toDouble() * 100.0; +} + +double SvgImport::fromPercentage( TQString s ) +{ + if( s.endsWith( "%" ) ) + return s.remove( '%' ).toDouble() / 100.0; + else + return s.toDouble(); +} + +double SvgImport::getScalingFromMatrix( TQWMatrix &matrix ) +{ + double xscale = matrix.m11() + matrix.m12(); + double yscale = matrix.m22() + matrix.m21(); + return sqrt( xscale*xscale + yscale*yscale ) / sqrt( 2.0 ); +} + +// parses the number into parameter number +const char * getNumber( const char *ptr, double &number ) +{ + int integer, exponent; + double decimal, frac; + int sign, expsign; + + exponent = 0; + integer = 0; + frac = 1.0; + decimal = 0; + sign = 1; + expsign = 1; + + // read the sign + if(*ptr == '+') + ptr++; + else if(*ptr == '-') + { + ptr++; + sign = -1; + } + + // read the integer part + while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9') + integer = (integer * 10) + *(ptr++) - '0'; + if(*ptr == '.') // read the decimals + { + ptr++; + while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9') + decimal += (*(ptr++) - '0') * (frac *= 0.1); + } + + if(*ptr == 'e' || *ptr == 'E') // read the exponent part + { + ptr++; + + // read the sign of the exponent + if(*ptr == '+') + ptr++; + else if(*ptr == '-') + { + ptr++; + expsign = -1; + } + + exponent = 0; + while(*ptr != '\0' && *ptr >= '0' && *ptr <= '9') + { + exponent *= 10; + exponent += *ptr - '0'; + ptr++; + } + } + number = integer + decimal; + number *= sign * pow( (double)10, double( expsign * exponent ) ); + + return ptr; +} + +void SvgImport::addGraphicContext() +{ + SvgGraphicsContext *gc = new SvgGraphicsContext; + // set as default + if( m_gc.current() ) + *gc = *( m_gc.current() ); + m_gc.push( gc ); +} + +void SvgImport::setupTransform( const TQDomElement &e ) +{ + SvgGraphicsContext *gc = m_gc.current(); + + TQWMatrix mat = VPath::parseTransform( e.attribute( "transform" ) ); + gc->matrix = mat * gc->matrix; +} + +VObject* SvgImport::findObject( const TQString &name, VGroup* group ) +{ + if( ! group ) + return 0L; + + VObjectListIterator itr = group->objects(); + + for( uint objcount = 1; itr.current(); ++itr, objcount++ ) + if( itr.current()->state() != VObject::deleted ) + { + if( itr.current()->name() == name ) + return itr.current(); + + if( dynamic_cast<VGroup *>( itr.current() ) ) + { + VObject *obj = findObject( name, dynamic_cast<VGroup *>( itr.current() ) ); + if( obj ) + return obj; + } + } + + return 0L; +} + +VObject* SvgImport::findObject( const TQString &name ) +{ + TQPtrVector<VLayer> vector; + m_document.layers().toVector( &vector ); + for( int i = vector.count() - 1; i >= 0; i-- ) + { + if ( vector[i]->state() != VObject::deleted ) + { + VObject* obj = findObject( name, dynamic_cast<VGroup *>( vector[i] ) ); + if( obj ) + return obj; + } + } + + return 0L; +} + +SvgImport::GradientHelper* SvgImport::findGradient( const TQString &id, const TQString &href) +{ + // check if gradient was already parsed, and return it + if( m_gradients.contains( id ) ) + return &m_gradients[ id ]; + + // check if gradient was stored for later parsing + if( !m_defs.contains( id ) ) + return 0L; + + TQDomElement e = m_defs[ id ]; + if(e.childNodes().count() == 0) + { + TQString mhref = e.attribute("xlink:href").mid(1); + + if(m_defs.contains(mhref)) + return findGradient(mhref, id); + else + return 0L; + } + else + { + // ok parse gradient now + parseGradient( m_defs[ id ], m_defs[ href ] ); + } + + // return successfully parsed gradient or NULL + TQString n; + if(href.isEmpty()) + n = id; + else + n = href; + + if( m_gradients.contains( n ) ) + return &m_gradients[ n ]; + else + return 0L; +} + +TQDomElement SvgImport::mergeStyles( const TQDomElement &referencedBy, const TQDomElement &referencedElement ) +{ + // First use all the style attributes of the element being referenced. + TQDomElement e = referencedElement; + + // Now go through the style attributes of the element that is referencing and substitute the original ones. + if( !referencedBy.attribute( "color" ).isEmpty() ) + e.setAttribute( "color", referencedBy.attribute( "color" ) ); + if( !referencedBy.attribute( "fill" ).isEmpty() ) + e.setAttribute( "fill", referencedBy.attribute( "fill" ) ); + if( !referencedBy.attribute( "fill-rule" ).isEmpty() ) + e.setAttribute( "fill-rule", referencedBy.attribute( "fill-rule" ) ); + if( !referencedBy.attribute( "stroke" ).isEmpty() ) + e.setAttribute( "stroke", referencedBy.attribute( "stroke" ) ); + if( !referencedBy.attribute( "stroke-width" ).isEmpty() ) + e.setAttribute( "stroke-width", referencedBy.attribute( "stroke-width" ) ); + if( !referencedBy.attribute( "stroke-linejoin" ).isEmpty() ) + e.setAttribute( "stroke-linejoin", referencedBy.attribute( "stroke-linejoin" ) ); + if( !referencedBy.attribute( "stroke-linecap" ).isEmpty() ) + e.setAttribute( "stroke-linecap", referencedBy.attribute( "stroke-linecap" ) ); + if( !referencedBy.attribute( "stroke-dasharray" ).isEmpty() ) + e.setAttribute( "stroke-dasharray", referencedBy.attribute( "stroke-dasharray" ) ); + if( !referencedBy.attribute( "stroke-dashoffset" ).isEmpty() ) + e.setAttribute( "stroke-dashoffset", referencedBy.attribute( "stroke-dashoffset" ) ); + if( !referencedBy.attribute( "stroke-opacity" ).isEmpty() ) + e.setAttribute( "stroke-opacity", referencedBy.attribute( "stroke-opacity" ) ); + if( !referencedBy.attribute( "stroke-miterlimit" ).isEmpty() ) + e.setAttribute( "stroke-miterlimit", referencedBy.attribute( "stroke-miterlimit" ) ); + if( !referencedBy.attribute( "fill-opacity" ).isEmpty() ) + e.setAttribute( "fill-opacity", referencedBy.attribute( "fill-opacity" ) ); + if( !referencedBy.attribute( "opacity" ).isEmpty() ) + e.setAttribute( "opacity", referencedBy.attribute( "opacity" ) ); + + // TODO merge style attribute too. + + return e; +} + + +// Parsing functions +// --------------------------------------------------------------------------------------- + +double SvgImport::parseUnit( const TQString &unit, bool horiz, bool vert, KoRect bbox ) +{ + // TODO : percentage? + double value = 0; + const char *start = unit.latin1(); + if(!start) { + return 0; + } + const char *end = getNumber( start, value ); + + if( uint( end - start ) < unit.length() ) + { + if( unit.right( 2 ) == "pt" ) + value = ( value / 72.0 ) * DPI; + else if( unit.right( 2 ) == "cm" ) + value = ( value / 2.54 ) * DPI; + else if( unit.right( 2 ) == "pc" ) + value = ( value / 6.0 ) * DPI; + else if( unit.right( 2 ) == "mm" ) + value = ( value / 25.4 ) * DPI; + else if( unit.right( 2 ) == "in" ) + value = value * DPI; + else if( unit.right( 2 ) == "pt" ) + value = ( value / 72.0 ) * DPI; + else if( unit.right( 2 ) == "em" ) + value = value * m_gc.current()->font.pointSize() / ( sqrt( pow( m_gc.current()->matrix.m11(), 2 ) + pow( m_gc.current()->matrix.m22(), 2 ) ) / sqrt( 2.0 ) ); + else if( unit.right( 1 ) == "%" ) + { + if( horiz && vert ) + value = ( value / 100.0 ) * (sqrt( pow( bbox.width(), 2 ) + pow( bbox.height(), 2 ) ) / sqrt( 2.0 ) ); + else if( horiz ) + value = ( value / 100.0 ) * bbox.width(); + else if( vert ) + value = ( value / 100.0 ) * bbox.height(); + } + } + /*else + { + if( m_gc.current() ) + { + if( horiz && vert ) + value *= sqrt( pow( m_gc.current()->matrix.m11(), 2 ) + pow( m_gc.current()->matrix.m22(), 2 ) ) / sqrt( 2.0 ); + else if( horiz ) + value /= m_gc.current()->matrix.m11(); + else if( vert ) + value /= m_gc.current()->matrix.m22(); + } + }*/ + return value; +} + +TQColor SvgImport::parseColor( const TQString &rgbColor ) +{ + int r, g, b; + keywordToRGB( rgbColor, r, g, b ); + return TQColor( r, g, b ); +} + +void SvgImport::parseColor( VColor &color, const TQString &s ) +{ + if( s.startsWith( "rgb(" ) ) + { + TQString parse = s.stripWhiteSpace(); + TQStringList colors = TQStringList::split( ',', parse ); + TQString r = colors[0].right( ( colors[0].length() - 4 ) ); + TQString g = colors[1]; + TQString b = colors[2].left( ( colors[2].length() - 1 ) ); + + if( r.contains( "%" ) ) + { + r = r.left( r.length() - 1 ); + r = TQString::number( int( ( double( 255 * r.toDouble() ) / 100.0 ) ) ); + } + + if( g.contains( "%" ) ) + { + g = g.left( g.length() - 1 ); + g = TQString::number( int( ( double( 255 * g.toDouble() ) / 100.0 ) ) ); + } + + if( b.contains( "%" ) ) + { + b = b.left( b.length() - 1 ); + b = TQString::number( int( ( double( 255 * b.toDouble() ) / 100.0 ) ) ); + } + + TQColor c( r.toInt(), g.toInt(), b.toInt() ); + color.set( c.red() / 255.0, c.green() / 255.0, c.blue() / 255.0 ); + } + else if( s == "currentColor" ) + { + SvgGraphicsContext *gc = m_gc.current(); + color = gc->color; + } + else + { + TQString rgbColor = s.stripWhiteSpace(); + TQColor c; + if( rgbColor.startsWith( "#" ) ) + c.setNamedColor( rgbColor ); + else + c = parseColor( rgbColor ); + color.set( c.red() / 255.0, c.green() / 255.0, c.blue() / 255.0 ); + } +} + +void SvgImport::parseColorStops( VGradient *gradient, const TQDomElement &e ) +{ + VColor c; + for( TQDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling() ) + { + TQDomElement stop = n.toElement(); + if( stop.tagName() == "stop" ) + { + float offset; + TQString temp = stop.attribute( "offset" ); + if( temp.contains( '%' ) ) + { + temp = temp.left( temp.length() - 1 ); + offset = temp.toFloat() / 100.0; + } + else + offset = temp.toFloat(); + + if( !stop.attribute( "stop-color" ).isEmpty() ) + parseColor( c, stop.attribute( "stop-color" ) ); + else + { + // try style attr + TQString style = stop.attribute( "style" ).simplifyWhiteSpace(); + TQStringList substyles = TQStringList::split( ';', style ); + for( TQStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it ) + { + TQStringList substyle = TQStringList::split( ':', (*it) ); + TQString command = substyle[0].stripWhiteSpace(); + TQString params = substyle[1].stripWhiteSpace(); + if( command == "stop-color" ) + parseColor( c, params ); + if( command == "stop-opacity" ) + c.setOpacity( params.toDouble() ); + } + + } + if( !stop.attribute( "stop-opacity" ).isEmpty() ) + c.setOpacity( stop.attribute( "stop-opacity" ).toDouble() ); + gradient->addStop( c, offset, 0.5 ); + } + } +} + +void SvgImport::parseGradient( const TQDomElement &e , const TQDomElement &referencedBy) +{ + // IMPROVEMENTS: + // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. + // - A gradient inherits attributes it does not have from the referencing gradient. + // - Gradients with no color stops have no fill or stroke. + // - Gradients with one color stop have a solid color. + + SvgGraphicsContext *gc = m_gc.current(); + if( !gc ) return; + + GradientHelper gradhelper; + gradhelper.gradient.clearStops(); + gradhelper.gradient.setRepeatMethod( VGradient::none ); + + if(e.childNodes().count() == 0) + { + TQString href = e.attribute("xlink:href").mid(1); + + if(href.isEmpty()) + { + //gc->fill.setType( VFill::none ); // <--- TODO Fill OR Stroke are none + return; + } + else + { + // copy the referenced gradient if found + GradientHelper *pGrad = findGradient( href ); + if( pGrad ) + gradhelper = *pGrad; + } + } + + // Use the gradient that is referencing, or if there isn't one, the original gradient. + TQDomElement b; + if( !referencedBy.isNull() ) + b = referencedBy; + else + b = e; + + TQString id = b.attribute("id"); + if( !id.isEmpty() ) + { + // Copy existing gradient if it exists + if( m_gradients.find( id ) != m_gradients.end() ) + gradhelper.gradient = m_gradients[ id ].gradient; + } + + gradhelper.bbox = b.attribute( "gradientUnits" ) != "userSpaceOnUse"; + + // parse color prop + VColor c = m_gc.current()->color; + + if( !b.attribute( "color" ).isEmpty() ) + { + parseColor( c, b.attribute( "color" ) ); + } + else + { + // try style attr + TQString style = b.attribute( "style" ).simplifyWhiteSpace(); + TQStringList substyles = TQStringList::split( ';', style ); + for( TQStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it ) + { + TQStringList substyle = TQStringList::split( ':', (*it) ); + TQString command = substyle[0].stripWhiteSpace(); + TQString params = substyle[1].stripWhiteSpace(); + if( command == "color" ) + parseColor( c, params ); + } + } + m_gc.current()->color = c; + + if( b.tagName() == "linearGradient" ) + { + if( gradhelper.bbox ) + { + gradhelper.gradient.setOrigin( KoPoint( toPercentage( b.attribute( "x1", "0%" ) ), toPercentage( b.attribute( "y1", "0%" ) ) ) ); + gradhelper.gradient.setVector( KoPoint( toPercentage( b.attribute( "x2", "100%" ) ), toPercentage( b.attribute( "y2", "0%" ) ) ) ); + } + else + { + gradhelper.gradient.setOrigin( KoPoint( b.attribute( "x1" ).toDouble(), b.attribute( "y1" ).toDouble() ) ); + gradhelper.gradient.setVector( KoPoint( b.attribute( "x2" ).toDouble(), b.attribute( "y2" ).toDouble() ) ); + } + gradhelper.gradient.setType( VGradient::linear ); + } + else + { + if( gradhelper.bbox ) + { + gradhelper.gradient.setOrigin( KoPoint( toPercentage( b.attribute( "cx", "50%" ) ), toPercentage( b.attribute( "cy", "50%" ) ) ) ); + gradhelper.gradient.setVector( KoPoint( toPercentage( b.attribute( "cx", "50%" ) ) + toPercentage( b.attribute( "r", "50%" ) ), toPercentage( b.attribute( "cy", "50%" ) ) ) ); + gradhelper.gradient.setFocalPoint( KoPoint( toPercentage( b.attribute( "fx", "50%" ) ), toPercentage( b.attribute( "fy", "50%" ) ) ) ); + } + else + { + gradhelper.gradient.setOrigin( KoPoint( b.attribute( "cx" ).toDouble(), b.attribute( "cy" ).toDouble() ) ); + gradhelper.gradient.setFocalPoint( KoPoint( b.attribute( "fx" ).toDouble(), b.attribute( "fy" ).toDouble() ) ); + gradhelper.gradient.setVector( KoPoint( b.attribute( "cx" ).toDouble() + b.attribute( "r" ).toDouble(), b.attribute( "cy" ).toDouble() ) ); + } + gradhelper.gradient.setType( VGradient::radial ); + } + // handle spread method + TQString spreadMethod = b.attribute( "spreadMethod" ); + if( !spreadMethod.isEmpty() ) + { + if( spreadMethod == "reflect" ) + gradhelper.gradient.setRepeatMethod( VGradient::reflect ); + else if( spreadMethod == "repeat" ) + gradhelper.gradient.setRepeatMethod( VGradient::repeat ); + else + gradhelper.gradient.setRepeatMethod( VGradient::none ); + } + else + gradhelper.gradient.setRepeatMethod( VGradient::none ); + + // Parse the color stops. The referencing gradient does not have colorstops, + // so use the stops from the gradient it references to (e in this case and not b) + parseColorStops( &gradhelper.gradient, e ); + //gradient.setGradientTransform( parseTransform( e.attribute( "gradientTransform" ) ) ); + gradhelper.gradientTransform = VPath::parseTransform( b.attribute( "gradientTransform" ) ); + m_gradients.insert( b.attribute( "id" ), gradhelper ); +} + +void SvgImport::parsePA( VObject *obj, SvgGraphicsContext *gc, const TQString &command, const TQString ¶ms ) +{ + VColor fillcolor = gc->fill.color(); + VColor strokecolor = gc->stroke.color(); + + if( params == "inherit" ) return; + + if( command == "fill" ) + { + if( params == "none" ) + gc->fill.setType( VFill::none ); + else if( params.startsWith( "url(" ) ) + { + unsigned int start = params.find("#") + 1; + unsigned int end = params.findRev(")"); + TQString key = params.mid( start, end - start ); + GradientHelper *gradHelper = findGradient( key ); + if( gradHelper ) + { + gc->fill.gradient() = gradHelper->gradient; + + if( gradHelper->bbox ) + { + // adjust to bbox + KoRect bbox = obj->boundingBox(); + //kdDebug() << "bbox x : " << bbox.x() << endl; + //kdDebug() << "!!!!!!bbox y : " << bbox.y() << endl; + //kdDebug() << gc->fill.gradient().origin().x() << endl; + //kdDebug() << gc->fill.gradient().vector().x() << endl; + double offsetx = parseUnit( TQString( "%1%" ).arg( gc->fill.gradient().origin().x() ), true, false, bbox ); + double offsety = parseUnit( TQString( "%1%" ).arg( gc->fill.gradient().origin().y() ), false, true, bbox ); + gc->fill.gradient().setOrigin( KoPoint( bbox.x() + offsetx, bbox.y() + offsety ) ); + if(gc->fill.gradient().type() == VGradient::radial) + { + offsetx = parseUnit( TQString( "%1%" ).arg( gc->fill.gradient().focalPoint().x() ), true, false, bbox ); + offsety = parseUnit( TQString( "%1%" ).arg( gc->fill.gradient().focalPoint().y() ), false, true, bbox ); + gc->fill.gradient().setFocalPoint( KoPoint( bbox.x() + offsetx, bbox.y() + offsety ) ); + } + offsetx = parseUnit( TQString( "%1%" ).arg( gc->fill.gradient().vector().x() ), true, false, bbox ); + offsety = parseUnit( TQString( "%1%" ).arg( gc->fill.gradient().vector().y() ), false, true, bbox ); + gc->fill.gradient().setVector( KoPoint( bbox.x() + offsetx, bbox.y() + offsety ) ); + //kdDebug() << offsety << endl; + //kdDebug() << gc->fill.gradient().origin().x() << endl; + //kdDebug() << gc->fill.gradient().origin().y() << endl; + //kdDebug() << gc->fill.gradient().vector().x() << endl; + //kdDebug() << gc->fill.gradient().vector().y() << endl; + } + gc->fill.gradient().transform( gradHelper->gradientTransform ); + + if( !gradHelper->bbox ) + gc->fill.gradient().transform( gc->matrix ); + + gc->fill.setType( VFill::grad ); + } + else + gc->fill.setType( VFill::none ); + } + else + { + parseColor( fillcolor, params ); + gc->fill.setType( VFill::solid ); + } + } + else if( command == "fill-rule" ) + { + if( params == "nonzero" ) + gc->fillRule = winding; + else if( params == "evenodd" ) + gc->fillRule = evenOdd; + } + else if( command == "stroke" ) + { + if( params == "none" ) + gc->stroke.setType( VStroke::none ); + else if( params.startsWith( "url(" ) ) + { + unsigned int start = params.find("#") + 1; + unsigned int end = params.findRev(")"); + TQString key = params.mid( start, end - start ); + + GradientHelper *gradHelper = findGradient( key ); + if( gradHelper ) + { + gc->stroke.gradient() = gradHelper->gradient; + gc->stroke.gradient().transform( gradHelper->gradientTransform ); + gc->stroke.gradient().transform( gc->matrix ); + gc->stroke.setType( VStroke::grad ); + } + else + gc->stroke.setType( VStroke::none ); + } + else + { + parseColor( strokecolor, params ); + gc->stroke.setType( VStroke::solid ); + } + } + else if( command == "stroke-width" ) + gc->stroke.setLineWidth( parseUnit( params, true, true, m_outerRect ) ); + else if( command == "stroke-linejoin" ) + { + if( params == "miter" ) + gc->stroke.setLineJoin( VStroke::joinMiter ); + else if( params == "round" ) + gc->stroke.setLineJoin( VStroke::joinRound ); + else if( params == "bevel" ) + gc->stroke.setLineJoin( VStroke::joinBevel ); + } + else if( command == "stroke-linecap" ) + { + if( params == "butt" ) + gc->stroke.setLineCap( VStroke::capButt ); + else if( params == "round" ) + gc->stroke.setLineCap( VStroke::capRound ); + else if( params == "square" ) + gc->stroke.setLineCap( VStroke::capSquare ); + } + else if( command == "stroke-miterlimit" ) + gc->stroke.setMiterLimit( params.toFloat() ); + else if( command == "stroke-dasharray" ) + { + TQValueList<float> array; + if(params != "none") + { + // with "stroke-dasharray", the separator is a white space + // inside style attribute, stroke-dasharray is separated by comma (,) + TQStringList dashes = TQStringList::split( TQRegExp("[\\s,]"), params ); + for( TQStringList::Iterator it = dashes.begin(); it != dashes.end(); ++it ) + array.append( (*it).toFloat() ); + } + gc->stroke.dashPattern().setArray( array ); + } + else if( command == "stroke-dashoffset" ) + gc->stroke.dashPattern().setOffset( params.toFloat() ); + // handle opacity + else if( command == "stroke-opacity" ) + strokecolor.setOpacity( fromPercentage( params ) ); + else if( command == "fill-opacity" ) + fillcolor.setOpacity( fromPercentage( params ) ); + else if( command == "opacity" ) + { + fillcolor.setOpacity( fromPercentage( params ) ); + strokecolor.setOpacity( fromPercentage( params ) ); + } + else if( command == "font-family" ) + { + TQString family = params; + family.replace( '\'' , ' ' ); + gc->font.setFamily( family ); + } + else if( command == "font-size" ) + { + float pointSize = parseUnit( params ); + gc->font.setPointSizeFloat( pointSize * getScalingFromMatrix( gc->matrix ) ); + } + else if( command == "font-weight" ) + { + int weight = TQFont::Normal; + + // map svg weight to qt weight + // svg value qt value + // 100,200,300 1, 17, 33 + // 400 50 (normal) + // 500,600 58,66 + // 700 75 (bold) + // 800,900 87,99 + + if( params == "bold" ) + weight = TQFont::Bold; + else if( params == "lighter" ) + { + weight = gc->font.weight(); + if( weight <= 17 ) + weight = 1; + else if( weight <= 33 ) + weight = 17; + else if( weight <= 50 ) + weight = 33; + else if( weight <= 58 ) + weight = 50; + else if( weight <= 66 ) + weight = 58; + else if( weight <= 75 ) + weight = 66; + else if( weight <= 87 ) + weight = 75; + else if( weight <= 99 ) + weight = 87; + } + else if( params == "bolder" ) + { + weight = gc->font.weight(); + if( weight >= 87 ) + weight = 99; + else if( weight >= 75 ) + weight = 87; + else if( weight >= 66 ) + weight = 75; + else if( weight >= 58 ) + weight = 66; + else if( weight >= 50 ) + weight = 58; + else if( weight >= 33 ) + weight = 50; + else if( weight >= 17 ) + weight = 50; + else if( weight >= 1 ) + weight = 17; + } + else + { + bool ok; + // try to read numerical weight value + weight = params.toInt( &ok, 10 ); + + if( !ok ) + return; + + switch( weight ) + { + case 100: weight = 1; break; + case 200: weight = 17; break; + case 300: weight = 33; break; + case 400: weight = 50; break; + case 500: weight = 58; break; + case 600: weight = 66; break; + case 700: weight = 75; break; + case 800: weight = 87; break; + case 900: weight = 99; break; + } + } + gc->font.setWeight( weight ); + } + else if( command == "text-decoration" ) + { + if( params == "line-through" ) + gc->font.setStrikeOut( true ); + else if( params == "underline" ) + gc->font.setUnderline( true ); + } + else if( command == "color" ) + { + VColor color; + parseColor( color, params ); + gc->color = color; + } + if( gc->fill.type() != VFill::none ) + gc->fill.setColor( fillcolor, false ); + //if( gc->stroke.type() == VStroke::solid ) + gc->stroke.setColor( strokecolor ); +} + +void SvgImport::parseStyle( VObject *obj, const TQDomElement &e ) +{ + SvgGraphicsContext *gc = m_gc.current(); + if( !gc ) return; + + // try normal PA + if( !e.attribute( "color" ).isEmpty() ) + parsePA( obj, gc, "color", e.attribute( "color" ) ); + if( !e.attribute( "fill" ).isEmpty() ) + parsePA( obj, gc, "fill", e.attribute( "fill" ) ); + if( !e.attribute( "fill-rule" ).isEmpty() ) + parsePA( obj, gc, "fill-rule", e.attribute( "fill-rule" ) ); + if( !e.attribute( "stroke" ).isEmpty() ) + parsePA( obj, gc, "stroke", e.attribute( "stroke" ) ); + if( !e.attribute( "stroke-width" ).isEmpty() ) + parsePA( obj, gc, "stroke-width", e.attribute( "stroke-width" ) ); + if( !e.attribute( "stroke-linejoin" ).isEmpty() ) + parsePA( obj, gc, "stroke-linejoin", e.attribute( "stroke-linejoin" ) ); + if( !e.attribute( "stroke-linecap" ).isEmpty() ) + parsePA( obj, gc, "stroke-linecap", e.attribute( "stroke-linecap" ) ); + if( !e.attribute( "stroke-dasharray" ).isEmpty() ) + parsePA( obj, gc, "stroke-dasharray", e.attribute( "stroke-dasharray" ) ); + if( !e.attribute( "stroke-dashoffset" ).isEmpty() ) + parsePA( obj, gc, "stroke-dashoffset", e.attribute( "stroke-dashoffset" ) ); + if( !e.attribute( "stroke-opacity" ).isEmpty() ) + parsePA( obj, gc, "stroke-opacity", e.attribute( "stroke-opacity" ) ); + if( !e.attribute( "stroke-miterlimit" ).isEmpty() ) + parsePA( obj, gc, "stroke-miterlimit", e.attribute( "stroke-miterlimit" ) ); + if( !e.attribute( "fill-opacity" ).isEmpty() ) + parsePA( obj, gc, "fill-opacity", e.attribute( "fill-opacity" ) ); + if( !e.attribute( "opacity" ).isEmpty() ) + parsePA( obj, gc, "opacity", e.attribute( "opacity" ) ); + + // try style attr + TQString style = e.attribute( "style" ).simplifyWhiteSpace(); + TQStringList substyles = TQStringList::split( ';', style ); + for( TQStringList::Iterator it = substyles.begin(); it != substyles.end(); ++it ) + { + TQStringList substyle = TQStringList::split( ':', (*it) ); + TQString command = substyle[0].stripWhiteSpace(); + TQString params = substyle[1].stripWhiteSpace(); + parsePA( obj, gc, command, params ); + } + + if(!obj) + return; + + obj->setFill( gc->fill ); + if( dynamic_cast<VPath *>( obj ) ) + dynamic_cast<VPath *>( obj )->setFillRule( gc->fillRule ); + // stroke scaling + double lineWidth = gc->stroke.lineWidth(); + gc->stroke.setLineWidth( lineWidth * getScalingFromMatrix( gc->matrix ) ); + obj->setStroke( gc->stroke ); + gc->stroke.setLineWidth( lineWidth ); +} + +void SvgImport::parseFont( const TQDomElement &e ) +{ + SvgGraphicsContext *gc = m_gc.current(); + if( !gc ) return; + + if( ! e.attribute( "font-family" ).isEmpty() ) + parsePA( 0L, m_gc.current(), "font-family", e.attribute( "font-family" ) ); + if( ! e.attribute( "font-size" ).isEmpty() ) + parsePA( 0L, m_gc.current(), "font-size", e.attribute( "font-size" ) ); + if( ! e.attribute( "font-weight" ).isEmpty() ) + parsePA( 0L, m_gc.current(), "font-weight", e.attribute( "font-weight" ) ); + if( ! e.attribute( "text-decoration" ).isEmpty() ) + parsePA( 0L, m_gc.current(), "text-decoration", e.attribute( "text-decoration" ) ); +} + +void SvgImport::parseUse( VGroup *grp, const TQDomElement &e ) +{ + TQString id = e.attribute( "xlink:href" ); + + if( !id.isEmpty() ) + { + addGraphicContext(); + setupTransform( e ); + + TQString key = id.mid( 1 ); + + if( !e.attribute( "x" ).isEmpty() && !e.attribute( "y" ).isEmpty() ) + { + double tx = e.attribute( "x" ).toDouble(); + double ty = e.attribute( "y" ).toDouble(); + + m_gc.current()->matrix.translate(tx,ty); + } + + if(m_defs.contains(key)) + { + TQDomElement a = m_defs[key]; + if(a.tagName() == "g" || a.tagName() == "a") + parseGroup( grp, a); + else + { + // Create the object with the merged styles. + // The object inherits all style attributes from the use tag, but keeps it's own attributes. + // So, not just use the style attributes of the use tag, but merge them first. + createObject( grp, a, VObject::normal, mergeStyles(e, a) ); + } + } + delete( m_gc.pop() ); + } +} + +void SvgImport::parseGroup( VGroup *grp, const TQDomElement &e ) +{ + for( TQDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling() ) + { + TQDomElement b = n.toElement(); + if( b.isNull() ) continue; + + // treat svg link <a> as group so we don't miss its child elements + if( b.tagName() == "g" || b.tagName() == "a" ) + { + VGroup *group; + if ( grp ) + group = new VGroup( grp ); + else + group = new VGroup( &m_document ); + + addGraphicContext(); + setupTransform( b ); + parseStyle( group, b ); + parseFont( b ); + parseGroup( group, b ); + + // handle id + if( !b.attribute("id").isEmpty() ) + group->setName( b.attribute("id") ); + if( grp ) + grp->append( group ); + else + m_document.append( group ); + delete( m_gc.pop() ); + continue; + } + if( b.tagName() == "defs" ) + { + parseDefs( b ); + continue; + } + else if( b.tagName() == "linearGradient" || b.tagName() == "radialGradient" ) + { + parseGradient( b ); + continue; + } + if( b.tagName() == "rect" || + b.tagName() == "ellipse" || + b.tagName() == "circle" || + b.tagName() == "line" || + b.tagName() == "polyline" || + b.tagName() == "polygon" || + b.tagName() == "path" || + b.tagName() == "image" ) + { + createObject( grp, b ); + continue; + } + else if( b.tagName() == "text" ) + { + createText( grp, b ); + continue; + } + else if( b.tagName() == "use" ) + { + parseUse( grp, b ); + continue; + } + } +} + +void SvgImport::parseDefs( const TQDomElement &e ) +{ + for( TQDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling() ) + { + TQDomElement b = n.toElement(); + if( b.isNull() ) continue; + + TQString definition = b.attribute( "id" ); + if( !definition.isEmpty() ) + { + if( !m_defs.contains( definition ) ) + m_defs.insert( definition, b ); + } + } +} + + +// Creating functions +// --------------------------------------------------------------------------------------- + +void SvgImport::createText( VGroup *grp, const TQDomElement &b ) +{ + const double pathLength = 10.0; + + VText *text = 0L; + TQString content; + TQString anchor; + VSubpath base( 0L ); + VPath *path = 0L; + double offset = 0.0; + + addGraphicContext(); + setupTransform( b ); + + parseFont( b ); + + if( ! b.attribute( "text-anchor" ).isEmpty() ) + anchor = b.attribute( "text-anchor" ); + + if( b.hasChildNodes() ) + { + if( base.isEmpty() && ! b.attribute( "x" ).isEmpty() && ! b.attribute( "y" ).isEmpty() ) + { + double x = parseUnit( b.attribute( "x" ) ); + double y = parseUnit( b.attribute( "y" ) ); + base.moveTo( KoPoint( x, y ) ); + base.lineTo( KoPoint( x + pathLength, y ) ); + } + + for( TQDomNode n = b.firstChild(); !n.isNull(); n = n.nextSibling() ) + { + TQDomElement e = n.toElement(); + if( e.isNull() ) + { + content += n.toCharacterData().data(); + } + else if( e.tagName() == "textPath" ) + { + if( e.attribute( "xlink:href" ).isEmpty() ) + continue; + + TQString key = e.attribute( "xlink:href" ).mid( 1 ); + if( ! m_defs.contains(key) ) + { + // try to find referenced object in document + VObject* obj = findObject( key ); + // try to find referenced object in actual group, which is not yet part of document + if( ! obj ) + obj = findObject( key, grp ); + if( obj ) + path = dynamic_cast<VPath*>( obj ); + } + else + { + TQDomElement p = m_defs[key]; + createObject( grp, p, VObject::deleted); + } + if( ! path ) + continue; + base = *path->paths().getFirst(); + content += e.text(); + + if( ! e.attribute( "startOffset" ).isEmpty() ) + { + TQString start = e.attribute( "startOffset" ); + if( start.endsWith( "%" ) ) + offset = 0.01 * start.remove( '%' ).toDouble(); + else + { + float pathLength = 0; + VSubpathIterator pIt( base ); + + for( ; pIt.current(); ++pIt ) + pathLength += pIt.current()->length(); + + if( pathLength > 0.0 ) + offset = start.toDouble() / pathLength; + } + } + } + else if( e.tagName() == "tspan" ) + { + // only use text of tspan element, as we are not supporting text + // with different styles + content += e.text(); + if( base.isEmpty() && ! e.attribute( "x" ).isEmpty() && ! e.attribute( "y" ).isEmpty() ) + { + TQStringList posX = TQStringList::split( ", ", e.attribute( "x" ) ); + TQStringList posY = TQStringList::split( ", ", e.attribute( "y" ) ); + if( posX.count() && posY.count() ) + { + double x = parseUnit( posX.first() ); + double y = parseUnit( posY.first() ); + base.moveTo( KoPoint( x, y ) ); + base.lineTo( KoPoint( x + pathLength, y ) ); + } + } + } + else if( e.tagName() == "tref" ) + { + if( e.attribute( "xlink:href" ).isEmpty() ) + continue; + + TQString key = e.attribute( "xlink:href" ).mid( 1 ); + if( ! m_defs.contains(key) ) + { + // try to find referenced object in document + VObject* obj = findObject( key ); + // try to find referenced object in actual group, which is not yet part of document + if( ! obj ) + obj = findObject( key, grp ); + if( obj ) + content += dynamic_cast<VText*>( obj )->text(); + } + else + { + TQDomElement p = m_defs[key]; + content += p.text(); + } + } + else + continue; + + if( ! e.attribute( "text-anchor" ).isEmpty() ) + anchor = e.attribute( "text-anchor" ); + } + text = new VText( m_gc.current()->font, base, VText::Above, VText::Left, content.simplifyWhiteSpace() ); + } + else + { + VSubpath base( 0L ); + double x = parseUnit( b.attribute( "x" ) ); + double y = parseUnit( b.attribute( "y" ) ); + base.moveTo( KoPoint( x, y ) ); + base.lineTo( KoPoint( x + pathLength, y ) ); + text = new VText( m_gc.current()->font, base, VText::Above, VText::Left, b.text().simplifyWhiteSpace() ); + } + + if( text ) + { + text->setParent( &m_document ); + + parseStyle( text, b ); + + text->setFont( m_gc.current()->font ); + + VTransformCmd trafo( 0L, m_gc.current()->matrix ); + trafo.visit( *text ); + + if( !b.attribute("id").isEmpty() ) + text->setName( b.attribute("id") ); + + if( anchor == "middle" ) + text->setAlignment( VText::Center ); + else if( anchor == "end" ) + text->setAlignment( VText::Right ); + + if( offset > 0.0 ) + text->setOffset( offset ); + + if( grp ) + grp->append( text ); + else + m_document.append( text ); + } + + delete( m_gc.pop() ); +} + +void SvgImport::createObject( VGroup *grp, const TQDomElement &b, const VObject::VState state, const TQDomElement &style ) +{ + VObject *obj = 0L; + + addGraphicContext(); + setupTransform( b ); + + if( b.tagName() == "rect" ) + { + double x = parseUnit( b.attribute( "x" ), true, false, m_outerRect ); + double y = parseUnit( b.attribute( "y" ), false, true, m_outerRect ); + double width = parseUnit( b.attribute( "width" ), true, false, m_outerRect ); + double height = parseUnit( b.attribute( "height" ), false, true, m_outerRect ); + double rx = parseUnit( b.attribute( "rx" ) ); + double ry = parseUnit( b.attribute( "ry" ) ); + obj = new VRectangle( 0L, KoPoint( x, height + y ) , width, height, rx, ry ); + } + else if( b.tagName() == "ellipse" ) + { + double rx = parseUnit( b.attribute( "rx" ) ); + double ry = parseUnit( b.attribute( "ry" ) ); + double left = parseUnit( b.attribute( "cx" ) ) - rx; + double top = parseUnit( b.attribute( "cy" ) ) - ry; + obj = new VEllipse( 0L, KoPoint( left, top ), rx * 2.0, ry * 2.0 ); + } + else if( b.tagName() == "circle" ) + { + double r = parseUnit( b.attribute( "r" ) ); + double left = parseUnit( b.attribute( "cx" ) ) - r; + double top = parseUnit( b.attribute( "cy" ) ) - r; + obj = new VEllipse( 0L, KoPoint( left, top ), r * 2.0, r * 2.0 ); + } + else if( b.tagName() == "line" ) + { + VPath *path = new VPath( &m_document ); + double x1 = b.attribute( "x1" ).isEmpty() ? 0.0 : parseUnit( b.attribute( "x1" ) ); + double y1 = b.attribute( "y1" ).isEmpty() ? 0.0 : parseUnit( b.attribute( "y1" ) ); + double x2 = b.attribute( "x2" ).isEmpty() ? 0.0 : parseUnit( b.attribute( "x2" ) ); + double y2 = b.attribute( "y2" ).isEmpty() ? 0.0 : parseUnit( b.attribute( "y2" ) ); + path->moveTo( KoPoint( x1, y1 ) ); + path->lineTo( KoPoint( x2, y2 ) ); + obj = path; + } + else if( b.tagName() == "polyline" || b.tagName() == "polygon" ) + { + VPath *path = new VPath( &m_document ); + bool bFirst = true; + + TQString points = b.attribute( "points" ).simplifyWhiteSpace(); + points.replace( ',', ' ' ); + points.remove( '\r' ); + points.remove( '\n' ); + TQStringList pointList = TQStringList::split( ' ', points ); + for( TQStringList::Iterator it = pointList.begin(); it != pointList.end(); ++it) + { + KoPoint point; + point.setX( (*it).toDouble() ); + ++it; + point.setY( (*it).toDouble() ); + if( bFirst ) + { + path->moveTo( point ); + bFirst = false; + } + else + path->lineTo( point ); + } + if( b.tagName() == "polygon" ) path->close(); + obj = path; + } + else if( b.tagName() == "path" ) + { + VPath *path = new VPath( &m_document ); + path->loadSvgPath( b.attribute( "d" ) ); + obj = path; + } + else if( b.tagName() == "image" ) + { + TQString fname = b.attribute("xlink:href"); + obj = new VImage( 0L, fname ); + } + + if( !obj ) + return; + + if (state != VObject::normal) + obj->setState(state); + + VTransformCmd trafo( 0L, m_gc.current()->matrix ); + trafo.visit( *obj ); + + if( !style.isNull() ) + parseStyle( obj, style ); + else + parseStyle( obj, b ); + + // handle id + if( !b.attribute("id").isEmpty() ) + obj->setName( b.attribute("id") ); + if( grp ) + grp->append( obj ); + else + m_document.append( obj ); + + delete( m_gc.pop() ); +} + +#include <svgimport.moc> |