/* 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 #include "color.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef KGenericFactory 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( itr.current() ) ) { VObject *obj = findObject( name, dynamic_cast( itr.current() ) ); if( obj ) return obj; } } return 0L; } VObject* SvgImport::findObject( const TQString &name ) { TQPtrVector 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( 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 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( obj ) ) dynamic_cast( 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 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( 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( 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