diff options
Diffstat (limited to 'filter/source/svg/svgreader.cxx')
-rw-r--r-- | filter/source/svg/svgreader.cxx | 2781 |
1 files changed, 2781 insertions, 0 deletions
diff --git a/filter/source/svg/svgreader.cxx b/filter/source/svg/svgreader.cxx new file mode 100644 index 000000000000..48fa362bde6d --- /dev/null +++ b/filter/source/svg/svgreader.cxx @@ -0,0 +1,2781 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/************************************************************************* + * + * OpenOffice.org - a multi-platform office productivity suite + * + * Author: + * Fridrich Strba <fridrich.strba@bluewin.ch> + * Thorsten Behrens <tbehrens@novell.com> + * + * Copyright (C) 2008, Novell Inc. + * Parts copyright 2005 by Sun Microsystems, Inc. + * + * The Contents of this file are made available subject to + * the terms of GNU Lesser General Public License Version 3. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_filter.hxx" + +#include "svgreader.hxx" +#include <xmloff/attrlist.hxx> +#include "gfxtypes.hxx" +#include "units.hxx" +#include "parserfragments.hxx" +#include "tokenmap.hxx" +#include "b2dellipse.hxx" + +#include <rtl/math.hxx> +#include <rtl/ref.hxx> +#include <rtl/ustring.hxx> +#include <rtl/ustrbuf.hxx> +#include <basegfx/vector/b2enums.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <com/sun/star/io/XSeekable.hpp> +#include <com/sun/star/xml/sax/XParser.hpp> +#include <com/sun/star/xml/dom/XDocumentBuilder.hpp> +#include <com/sun/star/xml/dom/NodeType.hpp> + +#include <comphelper/processfactory.hxx> +#include <basegfx/polygon/b2dpolygoncutandtouch.hxx> +#include <basegfx/polygon/b2dpolypolygoncutter.hxx> +#include <unotools/streamwrap.hxx> +#include <xmloff/xmluconv.hxx> +#include <vcl/graph.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gradient.hxx> +#include <svtools/filter.hxx> +#include <tools/zcodec.hxx> + +#include <boost/bind.hpp> +#include <hash_set> +#include <map> +#include <string.h> + +#define USTR(x) rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( x ) ) +#define OASIS_STR "urn:oasis:names:tc:opendocument:xmlns:" + +using namespace ::com::sun::star; + +namespace svgi +{ +namespace +{ + +/** visits all children of the specified type with the given functor + */ +template<typename Func> void visitChildren(const Func& rFunc, + const uno::Reference<xml::dom::XElement> xElem, + xml::dom::NodeType eChildType ) +{ + uno::Reference<xml::dom::XNodeList> xChildren( xElem->getChildNodes() ); + const sal_Int32 nNumNodes( xChildren->getLength() ); + for( sal_Int32 i=0; i<nNumNodes; ++i ) + { + if( xChildren->item(i)->getNodeType() == eChildType ) + rFunc( *xChildren->item(i).get() ); + } +} + +/** Visit all elements of the given tree (in-order traversal) + + Given functor is called for every element, and passed the + element's attributes, if any + */ +template<typename Func> void visitElements(Func& rFunc, + const uno::Reference<xml::dom::XElement> xElem) +{ + if( xElem->hasAttributes() ) + rFunc(xElem,xElem->getAttributes()); + else + rFunc(xElem); + + // notify children processing + rFunc.push(); + + // recurse over children + uno::Reference<xml::dom::XNodeList> xChildren( xElem->getChildNodes() ); + const sal_Int32 nNumNodes( xChildren->getLength() ); + for( sal_Int32 i=0; i<nNumNodes; ++i ) + { + if( xChildren->item(i)->getNodeType() == xml::dom::NodeType_ELEMENT_NODE ) + visitElements( rFunc, + uno::Reference<xml::dom::XElement>( + xChildren->item(i), + uno::UNO_QUERY_THROW) ); + } + + // children processing done + rFunc.pop(); +} + +template<typename value_type> value_type square(value_type v) +{ + return v*v; +} + +double colorDiffSquared(const ARGBColor& rCol1, const ARGBColor& rCol2) +{ + return + square(rCol1.a-rCol2.a) + + square(rCol1.r-rCol2.r) + + square(rCol1.g-rCol2.g) + + square(rCol1.b-rCol2.b); +} + +typedef std::map<rtl::OUString,sal_Size> ElementRefMapType; + +struct AnnotatingVisitor +{ + AnnotatingVisitor(StatePool& rStatePool, + StateMap& rStateMap, + const State& rInitialState, + const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler) : + mnCurrStateId(0), + maCurrState(), + maParentStates(), + mrStates(rStatePool), + mrStateMap(rStateMap), + mxDocumentHandler(xDocumentHandler), + maGradientVector(), + maGradientStopVector() + { + maParentStates.push_back(rInitialState); + } + + void operator()( const uno::Reference<xml::dom::XElement>& ) + {} + + void operator()( const uno::Reference<xml::dom::XElement>& xElem, + const uno::Reference<xml::dom::XNamedNodeMap>& xAttributes ) + { + const sal_Int32 nTagId(getTokenId(xElem->getTagName())); + switch (nTagId) + { + case XML_LINEARGRADIENT: + { + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + maGradientVector.push_back(Gradient(Gradient::LINEAR)); + + // do we have a reference to a parent gradient? parse + // that first, as it sets our defaults here (manually + // tracking default state on each Gradient variable is + // much more overhead) + uno::Reference<xml::dom::XNode> xNode(xAttributes->getNamedItem(USTR("href"))); + if(xNode.is()) + { + const rtl::OUString sValue(xNode->getNodeValue()); + ElementRefMapType::iterator aFound=maGradientIdMap.end(); + if (sValue.copy(0,1).equalsAscii("#")) + aFound = maGradientIdMap.find(sValue.copy(1)); + else + aFound = maGradientIdMap.find(sValue);; + + if( aFound != maGradientIdMap.end() ) + maGradientVector.back() = maGradientVector[aFound->second]; + } + + // do that after dereferencing, to prevent hyperlinked + // gradient to clobber our Id again + maGradientVector.back().mnId = maGradientVector.size()-1; + maGradientVector.back().meType = Gradient::LINEAR; // has been clobbered as well + + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + parseLinearGradientData( maGradientVector.back(), + maGradientVector.size()-1, + getTokenId(xAttributes->item(i)->getNodeName()), + xAttributes->item(i)->getNodeValue() ); + } + break; + } + case XML_RADIALGRADIENT: + { + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + maGradientVector.push_back(Gradient(Gradient::RADIAL)); + + // do we have a reference to a parent gradient? parse + // that first, as it sets our defaults here (manually + // tracking default state on each Gradient variable is + // much more overhead) + uno::Reference<xml::dom::XNode> xNode(xAttributes->getNamedItem(USTR("href"))); + if(xNode.is()) + { + const rtl::OUString sValue(xNode->getNodeValue()); + ElementRefMapType::iterator aFound=maGradientIdMap.end(); + if (sValue.copy(0,1).equalsAscii("#")) + aFound = maGradientIdMap.find(sValue.copy(1)); + else + aFound = maGradientIdMap.find(sValue);; + + if( aFound != maGradientIdMap.end() ) + maGradientVector.back() = maGradientVector[aFound->second]; + } + + // do that after dereferencing, to prevent hyperlinked + // gradient to clobber our Id again + maGradientVector.back().mnId = maGradientVector.size()-1; + maGradientVector.back().meType = Gradient::RADIAL; // has been clobbered as well + + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + parseRadialGradientData( maGradientVector.back(), + maGradientVector.size()-1, + getTokenId(xAttributes->item(i)->getNodeName()), + xAttributes->item(i)->getNodeValue() ); + } + break; + } + case XML_STOP: + { + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + maGradientStopVector.push_back(GradientStop()); + maGradientVector.back().maStops.push_back(maGradientStopVector.size()-1); + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + parseGradientStop( maGradientStopVector.back(), + maGradientStopVector.size()-1, + getTokenId(xAttributes->item(i)->getNodeName()), + xAttributes->item(i)->getNodeValue() ); + } + break; + } + default: + { + // init state. inherit defaults from parent. + maCurrState = maParentStates.back(); + maCurrState.maTransform.identity(); + maCurrState.maViewBox.reset(); + + // scan for style info + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nTokenId( + getTokenId(xAttributes->item(i)->getNodeName())); + if( XML_STYLE == nTokenId ) + parseStyle(sAttributeValue); + else + parseAttribute(nTokenId, + sAttributeValue); + } + + // all attributes parsed, can calc total CTM now + basegfx::B2DHomMatrix aLocalTransform; + if( !maCurrState.maViewBox.isEmpty() && + maCurrState.maViewBox.getWidth() != 0.0 && + maCurrState.maViewBox.getHeight() != 0.0 ) + { + // transform aViewBox into viewport, such that they + // coincide + aLocalTransform.translate(-maCurrState.maViewBox.getMinX(), + -maCurrState.maViewBox.getMinY()); + aLocalTransform.scale(maCurrState.maViewport.getWidth()/maCurrState.maViewBox.getWidth(), + maCurrState.maViewport.getHeight()/maCurrState.maViewBox.getHeight()); + } + maCurrState.maCTM = maCurrState.maCTM*maCurrState.maTransform*aLocalTransform; + + OSL_TRACE("annotateStyle - CTM is: %f %f %f %f %f %f", + maCurrState.maCTM.get(0,0), + maCurrState.maCTM.get(0,1), + maCurrState.maCTM.get(0,2), + maCurrState.maCTM.get(1,0), + maCurrState.maCTM.get(1,1), + maCurrState.maCTM.get(1,2)); + + // if necessary, serialize to automatic-style section + writeStyle(xElem,nTagId); + } + } + } + + rtl::OUString getStyleName( const char* sPrefix, sal_Int32 nId ) + { + return rtl::OUString::createFromAscii(sPrefix)+rtl::OUString::valueOf(nId); + } + + bool hasGradientOpacity( const Gradient& rGradient ) + { + return + !rGradient.maStops.empty() && + (maGradientStopVector[ + rGradient.maStops[0]].maStopColor.a != 1.0 || + maGradientStopVector[ + rGradient.maStops[1]].maStopColor.a != 1.0); + } + + struct StopSorter + { + explicit StopSorter( const std::vector< GradientStop >& rStopVec ) : + mrStopVec(rStopVec) + {} + + bool operator()( sal_Size rLHS, sal_Size rRHS ) + { + return mrStopVec[rLHS].mnStopPosition < mrStopVec[rRHS].mnStopPosition; + } + + const std::vector< GradientStop >& mrStopVec; + }; + + void optimizeGradientStops( Gradient& rGradient ) + { + // sort for increasing stop position + std::sort(rGradient.maStops.begin(),rGradient.maStops.end(), + StopSorter(maGradientStopVector)); + + if( rGradient.maStops.size() < 3 ) + return; //easy! :-) + + // join similar colors + std::vector<sal_Size> aNewStops(1,rGradient.maStops.front()); + for( sal_Size i=1; i<rGradient.maStops.size(); ++i ) + { + if( maGradientStopVector[rGradient.maStops[i]].maStopColor != + maGradientStopVector[aNewStops.back()].maStopColor ) + aNewStops.push_back(rGradient.maStops[i]); + } + + rGradient.maStops = aNewStops; + + // axial gradient, maybe? + if( rGradient.meType == Gradient::LINEAR && + rGradient.maStops.size() == 3 && + maGradientStopVector[rGradient.maStops.front()].maStopColor == + maGradientStopVector[rGradient.maStops.back()].maStopColor ) + { + // yep - keep it at that + return; + } + + // find out most significant color difference, and limit to + // those two stops around this border (metric is + // super-simplistic: take euclidean distance of colors, weigh + // with stop distance) + sal_Size nMaxIndex=0; + double fMaxDistance=0.0; + for( sal_Size i=1; i<rGradient.maStops.size(); ++i ) + { + const double fCurrDistance( + colorDiffSquared( + maGradientStopVector[rGradient.maStops[i-1]].maStopColor, + maGradientStopVector[rGradient.maStops[i]].maStopColor) * + (square(maGradientStopVector[rGradient.maStops[i-1]].mnStopPosition) + + square(maGradientStopVector[rGradient.maStops[i]].mnStopPosition)) ); + + if( fCurrDistance > fMaxDistance ) + { + nMaxIndex = i-1; + fMaxDistance = fCurrDistance; + } + } + rGradient.maStops[0] = rGradient.maStops[nMaxIndex]; + rGradient.maStops[1] = rGradient.maStops[nMaxIndex+1]; + rGradient.maStops.erase(rGradient.maStops.begin()+2,rGradient.maStops.end()); + } + + sal_Int8 toByteColor( double val ) + { + // TODO(Q3): duplicated from vcl::unotools + return sal::static_int_cast<sal_Int8>( + basegfx::fround(val*255.0)); + } + + rtl::OUString getOdfColor( const ARGBColor& rColor ) + { + // TODO(Q3): duplicated from pdfimport + rtl::OUStringBuffer aBuf( 7 ); + const sal_uInt8 nRed ( toByteColor(rColor.r) ); + const sal_uInt8 nGreen( toByteColor(rColor.g) ); + const sal_uInt8 nBlue ( toByteColor(rColor.b) ); + aBuf.append( sal_Unicode('#') ); + if( nRed < 10 ) + aBuf.append( sal_Unicode('0') ); + aBuf.append( sal_Int32(nRed), 16 ); + if( nGreen < 10 ) + aBuf.append( sal_Unicode('0') ); + aBuf.append( sal_Int32(nGreen), 16 ); + if( nBlue < 10 ) + aBuf.append( sal_Unicode('0') ); + aBuf.append( sal_Int32(nBlue), 16 ); + + // TODO(F3): respect alpha transparency (polygons etc.) + OSL_ASSERT(rColor.a == 1.0); + + return aBuf.makeStringAndClear(); + } + + rtl::OUString getOdfAlign( TextAlign eAlign ) + { + static ::rtl::OUString aStart(USTR("start")); + static ::rtl::OUString aEnd(USTR("end")); + // static ::rtl::OUString aJustify(USTR("justify")); + static ::rtl::OUString aCenter(USTR("center")); + switch(eAlign) + { + default: + case BEFORE: + return aStart; + case CENTER: + return aCenter; + case AFTER: + return aEnd; + } + } + + bool writeStyle(State& rState, const sal_Int32 nTagId) + { + rtl::Reference<SvXMLAttributeList> xAttrs( new SvXMLAttributeList() ); + uno::Reference<xml::sax::XAttributeList> xUnoAttrs( xAttrs.get() ); + + std::pair<StatePool::iterator, + bool> aRes = mrStates.insert(rState); + if( !aRes.second ) + return false; // not written + + ++mnCurrStateId; + + // mnStyleId does not take part in hashing/comparison + const_cast<State&>(*aRes.first).mnStyleId = mnCurrStateId; + mrStateMap.insert(std::make_pair( + mnCurrStateId, + rState)); + + // find two representative stop colors (as odf only support + // start&end color) + optimizeGradientStops(rState.maFillGradient); + + if( !mxDocumentHandler.is() ) + return true; // cannot write style, svm import case + + // do we have a gradient fill? then write out gradient as well + if( rState.meFillType == GRADIENT && rState.maFillGradient.maStops.size() > 1 ) + { + // TODO(F3): ODF12 supposedly also groks svg:linear/radialGradient. But CL says: nope. + xAttrs->AddAttribute( USTR( "draw:name" ), getStyleName("svggradient", rState.maFillGradient.mnId) ); + if( rState.maFillGradient.meType == Gradient::LINEAR ) + { + // should the optimizeGradientStops method decide that + // this is a three-color gradient, it prolly wanted us + // to take axial instead + xAttrs->AddAttribute( USTR( "draw:style" ), + rState.maFillGradient.maStops.size() == 3 ? + USTR("axial") : + USTR("linear") ); + } + else + { + xAttrs->AddAttribute( USTR( "draw:style" ), USTR("ellipsoid") ); + xAttrs->AddAttribute( USTR( "draw:cx" ), USTR("50%") ); + xAttrs->AddAttribute( USTR( "draw:cy" ), USTR("50%") ); + } + + basegfx::B2DTuple rScale, rTranslate; + double rRotate, rShearX; + if( rState.maFillGradient.maTransform.decompose(rScale, rTranslate, rRotate, rShearX) ) + xAttrs->AddAttribute( USTR( "draw:angle" ), + rtl::OUString::valueOf(rRotate*1800.0/M_PI ) ); + xAttrs->AddAttribute( USTR( "draw:start-color" ), + getOdfColor( + maGradientStopVector[ + rState.maFillGradient.maStops[0]].maStopColor) ); + xAttrs->AddAttribute( USTR( "draw:end-color" ), + getOdfColor( + maGradientStopVector[ + rState.maFillGradient.maStops[1]].maStopColor) ); + xAttrs->AddAttribute( USTR( "draw:border" ), USTR("0%") ); + mxDocumentHandler->startElement( USTR("draw:gradient"), + xUnoAttrs ); + mxDocumentHandler->endElement( USTR("draw:gradient") ); + + if( hasGradientOpacity(rState.maFillGradient) ) + { + // need to write out opacity style as well + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "draw:name" ), getStyleName("svgopacity", rState.maFillGradient.mnId) ); + if( rState.maFillGradient.meType == Gradient::LINEAR ) + { + xAttrs->AddAttribute( USTR( "draw:style" ), USTR("linear") ); + } + else + { + xAttrs->AddAttribute( USTR( "draw:style" ), USTR("ellipsoid") ); + xAttrs->AddAttribute( USTR( "draw:cx" ), USTR("50%") ); + xAttrs->AddAttribute( USTR( "draw:cy" ), USTR("50%") ); + } + + // modulate gradient opacity with overall fill opacity + xAttrs->AddAttribute( USTR( "draw:end" ), + rtl::OUString::valueOf( + maGradientStopVector[ + rState.maFillGradient.maStops[0]].maStopColor.a* + maCurrState.mnFillOpacity*maCurrState.mnOpacity*100.0)+USTR("%" ) ); + xAttrs->AddAttribute( USTR( "draw:start" ), + rtl::OUString::valueOf( + maGradientStopVector[ + rState.maFillGradient.maStops[1]].maStopColor.a* + maCurrState.mnFillOpacity*maCurrState.mnOpacity*100.0)+USTR("%" ) ); + xAttrs->AddAttribute( USTR( "draw:border" ), USTR("0%") ); + mxDocumentHandler->startElement( USTR("draw:opacity"), + xUnoAttrs ); + mxDocumentHandler->endElement( USTR("draw:opacity") ); + } + } + + // serialize to automatic-style section + if( nTagId == XML_TEXT ) + { + // write paragraph style attributes + xAttrs->Clear(); + + xAttrs->AddAttribute( USTR( "style:name" ), getStyleName("svgparagraphstyle", mnCurrStateId) ); + xAttrs->AddAttribute( USTR( "style:family" ), USTR("paragraph") ); + mxDocumentHandler->startElement( USTR("style:style"), + xUnoAttrs ); + + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "fo:text-align"), getOdfAlign(rState.meTextAnchor)); + + mxDocumentHandler->startElement( USTR("style:paragraph-properties"), + xUnoAttrs ); + mxDocumentHandler->endElement( USTR("style:paragraph-properties") ); + mxDocumentHandler->endElement( USTR("style:style") ); + + // write text style attributes + xAttrs->Clear(); + + xAttrs->AddAttribute( USTR( "style:name" ), getStyleName("svgtextstyle", mnCurrStateId) ); + xAttrs->AddAttribute( USTR( "style:family" ), USTR("text") ); + mxDocumentHandler->startElement( USTR("style:style"), + xUnoAttrs ); + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "fo:font-family"), rState.maFontFamily); + xAttrs->AddAttribute( USTR( "fo:font-size"), + rtl::OUString::valueOf(pt2mm(rState.mnFontSize))+USTR("mm")); + xAttrs->AddAttribute( USTR( "fo:font-style"), rState.maFontStyle); + xAttrs->AddAttribute( USTR( "fo:font-variant"), rState.maFontVariant); + xAttrs->AddAttribute( USTR( "fo:font-weight"), + rtl::OUString::valueOf(rState.mnFontWeight)); + xAttrs->AddAttribute( USTR( "fo:color"), getOdfColor(rState.maFillColor)); + + mxDocumentHandler->startElement( USTR("style:text-properties"), + xUnoAttrs ); + mxDocumentHandler->endElement( USTR("style:text-properties") ); + mxDocumentHandler->endElement( USTR("style:style") ); + } + + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "style:name" ), getStyleName("svggraphicstyle", mnCurrStateId) ); + xAttrs->AddAttribute( USTR( "style:family" ), USTR("graphic") ); + mxDocumentHandler->startElement( USTR("style:style"), + xUnoAttrs ); + + xAttrs->Clear(); + // text or shape? if the former, no use in processing any + // graphic attributes except stroke color, ODF can do ~nothing + // with text shapes + if( nTagId == XML_TEXT ) + { + //xAttrs->AddAttribute( USTR( "draw:auto-grow-height"), USTR("true")); + xAttrs->AddAttribute( USTR( "draw:auto-grow-width"), USTR("true")); + xAttrs->AddAttribute( USTR( "draw:textarea-horizontal-align"), USTR("left")); + //xAttrs->AddAttribute( USTR( "draw:textarea-vertical-align"), USTR("top")); + xAttrs->AddAttribute( USTR( "fo:min-height"), USTR("0cm")); + + xAttrs->AddAttribute( USTR( "fo:padding-top"), USTR("0cm")); + xAttrs->AddAttribute( USTR( "fo:padding-left"), USTR("0cm")); + xAttrs->AddAttribute( USTR( "fo:padding-right"), USTR("0cm")); + xAttrs->AddAttribute( USTR( "fo:padding-bottom"), USTR("0cm")); + + // disable any background shape + xAttrs->AddAttribute( USTR( "draw:stroke" ), USTR("none")); + xAttrs->AddAttribute( USTR( "draw:fill" ), USTR("none")); + } + else + { + if( rState.meFillType != NONE ) + { + if( rState.meFillType == GRADIENT ) + { + xAttrs->AddAttribute( USTR( "draw:fill" ), USTR("gradient")); + xAttrs->AddAttribute( USTR( "draw:fill-gradient-name" ), + getStyleName("svggradient", rState.maFillGradient.mnId) ); + if( hasGradientOpacity(rState.maFillGradient) ) + { + // needs transparency gradient as well + xAttrs->AddAttribute( USTR( "draw:opacity-name" ), + getStyleName("svgopacity", rState.maFillGradient.mnId) ); + } + else if( maCurrState.mnFillOpacity*maCurrState.mnOpacity != 1.0 ) + xAttrs->AddAttribute( USTR( "draw:opacity" ), + rtl::OUString::valueOf(100.0*maCurrState.mnFillOpacity*maCurrState.mnOpacity)+USTR("%") ); + } + else + { + xAttrs->AddAttribute( USTR( "draw:fill" ), USTR("solid")); + xAttrs->AddAttribute( USTR( "draw:fill-color" ), getOdfColor(rState.maFillColor)); + if( maCurrState.mnFillOpacity*maCurrState.mnOpacity != 1.0 ) + xAttrs->AddAttribute( USTR( "draw:opacity" ), + rtl::OUString::valueOf(100.0*maCurrState.mnFillOpacity*maCurrState.mnOpacity)+USTR("%") ); + } + } + else + xAttrs->AddAttribute( USTR( "draw:fill" ), USTR("none")); + + if( rState.meStrokeType != NONE ) + { + xAttrs->AddAttribute( USTR( "draw:stroke" ), USTR("solid")); + xAttrs->AddAttribute( USTR( "svg:stroke-color" ), getOdfColor(rState.maStrokeColor)); + } + else + xAttrs->AddAttribute( USTR( "draw:stroke" ), USTR("none")); + + if( maCurrState.mnStrokeWidth != 0.0 ) + { + ::basegfx::B2DVector aVec(maCurrState.mnStrokeWidth,0); + aVec *= maCurrState.maCTM; + xAttrs->AddAttribute( USTR("svg:stroke-width"), rtl::OUString::valueOf( pt2mm(aVec.getLength()) )+USTR("mm")); + } + if( maCurrState.meLineJoin == basegfx::B2DLINEJOIN_MITER ) + xAttrs->AddAttribute( USTR( "draw:stroke-linejoin"), USTR("miter")); + else if( maCurrState.meLineJoin == basegfx::B2DLINEJOIN_ROUND ) + xAttrs->AddAttribute( USTR( "draw:stroke-linejoin"), USTR("round")); + else if( maCurrState.meLineJoin == basegfx::B2DLINEJOIN_BEVEL ) + xAttrs->AddAttribute( USTR( "draw:stroke-linejoin"), USTR("bevel")); + if( maCurrState.mnStrokeOpacity*maCurrState.mnOpacity != 1.0 ) + xAttrs->AddAttribute( USTR("svg:stroke-opacity"), + rtl::OUString::valueOf(100.0*maCurrState.mnStrokeOpacity*maCurrState.mnOpacity)+USTR("%")); + } + + mxDocumentHandler->startElement( USTR("style:graphic-properties"), + xUnoAttrs ); + mxDocumentHandler->endElement( USTR("style:graphic-properties") ); + mxDocumentHandler->endElement( USTR("style:style") ); + + return true; // newly written + } + + void writeStyle(const uno::Reference<xml::dom::XElement>& xElem, const sal_Int32 nTagId) + { + sal_Int32 nEmulatedStyleId=0; + if( maCurrState.maDashArray.size() && + maCurrState.meStrokeType != NONE ) + { + // ODF dashing is severly borked - generate filled shape + // instead (further down the road - here, we simply + // emulate a filled style with the next id) + + // move all stroke attribs to fill, Clear stroking + State aEmulatedStrokeState( maCurrState ); + aEmulatedStrokeState.meFillType = maCurrState.meStrokeType; + aEmulatedStrokeState.mnFillOpacity = maCurrState.mnStrokeOpacity; + aEmulatedStrokeState.maFillColor = maCurrState.maStrokeColor; + aEmulatedStrokeState.maFillGradient = maCurrState.maStrokeGradient; + aEmulatedStrokeState.meFillRule = EVEN_ODD; + aEmulatedStrokeState.meStrokeType = NONE; + + if( writeStyle(aEmulatedStrokeState, nTagId) ) + nEmulatedStyleId = mnCurrStateId; + else + nEmulatedStyleId = mrStates.find(aEmulatedStrokeState)->mnStyleId; + } + + sal_Int32 nStyleId=0; + if( writeStyle(maCurrState, nTagId) ) + nStyleId = mnCurrStateId; + else + nStyleId = mrStates.find(maCurrState)->mnStyleId; + + xElem->setAttribute(USTR("internal-style-ref"), + rtl::OUString::valueOf( + nStyleId) + +USTR("$") + +rtl::OUString::valueOf( + nEmulatedStyleId)); + } + + void push() + { + maParentStates.push_back(maCurrState); + } + + void pop() + { + maParentStates.pop_back(); + } + + void parseLinearGradientData( Gradient& io_rCurrGradient, + const sal_Int32 nGradientNumber, + const sal_Int32 nTokenId, + const rtl::OUString& sValue ) + { + switch(nTokenId) + { + case XML_GRADIENTTRANSFORM: + { + rtl::OString aValueUtf8( sValue.getStr(), + sValue.getLength(), + RTL_TEXTENCODING_UTF8 ); + parseTransform(aValueUtf8.getStr(),io_rCurrGradient.maTransform); + break; + } + case XML_X1: + io_rCurrGradient.maCoords.linear.mfX1 = convLength(sValue,maCurrState,'h'); + break; + case XML_X2: + io_rCurrGradient.maCoords.linear.mfX2 = convLength(sValue,maCurrState,'h'); + break; + case XML_Y1: + io_rCurrGradient.maCoords.linear.mfY1 = convLength(sValue,maCurrState,'v'); + break; + case XML_Y2: + io_rCurrGradient.maCoords.linear.mfY2 = convLength(sValue,maCurrState,'v'); + break; + case XML_ID: + maGradientIdMap.insert(std::make_pair(sValue,nGradientNumber)); + break; + case XML_GRADIENTUNITS: + if (getTokenId(sValue) == XML_OBJECTBOUNDINGBOX) + io_rCurrGradient.mbBoundingBoxUnits = true; + else + io_rCurrGradient.mbBoundingBoxUnits = false; + break; + default: + break; + } + } + + void parseRadialGradientData( Gradient& io_rCurrGradient, + const sal_Int32 nGradientNumber, + const sal_Int32 nTokenId, + const rtl::OUString& sValue ) + { + switch(nTokenId) + { + case XML_GRADIENTTRANSFORM: + { + rtl::OString aValueUtf8( sValue.getStr(), + sValue.getLength(), + RTL_TEXTENCODING_UTF8 ); + parseTransform(aValueUtf8.getStr(),io_rCurrGradient.maTransform); + break; + } + case XML_CX: + io_rCurrGradient.maCoords.radial.mfCX = convLength(sValue,maCurrState,'h'); + break; + case XML_CY: + io_rCurrGradient.maCoords.radial.mfCY = convLength(sValue,maCurrState,'v'); + break; + case XML_FX: + io_rCurrGradient.maCoords.radial.mfFX = convLength(sValue,maCurrState,'h'); + break; + case XML_FY: + io_rCurrGradient.maCoords.radial.mfFY = convLength(sValue,maCurrState,'v'); + break; + case XML_R: + io_rCurrGradient.maCoords.radial.mfR = convLength(sValue,maCurrState,'r'); + break; + case XML_ID: + maGradientIdMap.insert(std::make_pair(sValue,nGradientNumber)); + break; + case XML_GRADIENTUNITS: + if (getTokenId(sValue) == XML_OBJECTBOUNDINGBOX) + io_rCurrGradient.mbBoundingBoxUnits = true; + else + io_rCurrGradient.mbBoundingBoxUnits = false; + break; + default: + break; + } + } + + void parseGradientStop( GradientStop& io_rGradientStop, + const sal_Int32 nStopNumber, + const sal_Int32 nTokenId, + const rtl::OUString& sValue ) + { + switch(nTokenId) + { + case XML_HREF: + { + ElementRefMapType::iterator aFound=maStopIdMap.end(); + if (sValue.copy(0,1).equalsAscii("#")) + aFound = maStopIdMap.find(sValue.copy(1)); + else + aFound = maStopIdMap.find(sValue);; + + if( aFound != maStopIdMap.end() ) + io_rGradientStop = maGradientStopVector[aFound->second]; + break; + } + case XML_ID: + maStopIdMap.insert(std::make_pair(sValue,nStopNumber)); + break; + case XML_OFFSET: + io_rGradientStop.mnStopPosition = sValue.toDouble(); + break; + case XML_STYLE: + parseStyle( sValue ); + break; + default: + break; + } + } + + void parseAttribute( const sal_Int32 nTokenId, + const rtl::OUString& sValue ) + { + rtl::OString aValueUtf8( sValue.getStr(), + sValue.getLength(), + RTL_TEXTENCODING_UTF8 ); + switch(nTokenId) + { + case XML_WIDTH: + { + const double fViewPortWidth( + convLength(sValue,maCurrState,'h')); + + maCurrState.maViewport.expand( + basegfx::B2DTuple(fViewPortWidth,0.0)); + break; + } + case XML_HEIGHT: + { + const double fViewPortHeight( + convLength(sValue,maCurrState,'v')); + + maCurrState.maViewport.expand( + basegfx::B2DTuple(0.0,fViewPortHeight)); + break; + } + case XML_VIEWBOX: + { + // TODO(F1): preserveAspectRatio + parseViewBox( + aValueUtf8, + maCurrState.maViewBox); + break; + } + case XML_FILL_RULE: + { + if( aValueUtf8 == "evenodd" ) + maCurrState.meFillRule = EVEN_ODD; + else if( aValueUtf8 == "nonzero" ) + maCurrState.meFillRule = NON_ZERO; + else if( aValueUtf8 == "inherit" ) + maCurrState.meFillRule = maParentStates.back().meFillRule; + break; + } + case XML_OPACITY: + if( aValueUtf8 == "inherit" ) + maCurrState.mnOpacity = maParentStates.back().mnOpacity; + else + maCurrState.mnOpacity = aValueUtf8.toDouble(); + break; + case XML_FILL_OPACITY: + if( aValueUtf8 == "inherit" ) + maCurrState.mnFillOpacity = maParentStates.back().mnFillOpacity; + else + maCurrState.mnFillOpacity = aValueUtf8.toDouble(); + break; + case XML_STROKE_WIDTH: + { + if( aValueUtf8 == "inherit" ) + maCurrState.mnStrokeWidth = maParentStates.back().mnStrokeWidth; + else + maCurrState.mnStrokeWidth = convLength(sValue,maCurrState,'r'); + break; + } + case XML_STROKE_LINECAP: + { + if( aValueUtf8 == "butt" ) + maCurrState.meLineCap = BUTT; + else if( aValueUtf8 == "round" ) + maCurrState.meLineCap = ROUND; + else if( aValueUtf8 == "square" ) + maCurrState.meLineCap = RECT; + else if( aValueUtf8 == "inherit" ) + maCurrState.meLineCap = maParentStates.back().meLineCap; + break; + } + case XML_STROKE_LINEJOIN: + { + if( aValueUtf8 == "miter" ) + maCurrState.meLineJoin = basegfx::B2DLINEJOIN_MITER; + else if( aValueUtf8 == "round" ) + maCurrState.meLineJoin = basegfx::B2DLINEJOIN_ROUND; + else if( aValueUtf8 == "bevel" ) + maCurrState.meLineJoin = basegfx::B2DLINEJOIN_BEVEL; + else if( aValueUtf8 == "inherit" ) + maCurrState.meLineJoin = maParentStates.back().meLineJoin; + break; + } + case XML_STROKE_MITERLIMIT: + { + if( aValueUtf8 == "inherit" ) + maCurrState.mnMiterLimit = maParentStates.back().mnMiterLimit; + else + maCurrState.mnMiterLimit = aValueUtf8.toDouble(); + break; + } + case XML_STROKE_DASHOFFSET: + { + if( aValueUtf8 == "inherit" ) + maCurrState.mnDashOffset = maParentStates.back().mnDashOffset; + else + maCurrState.mnDashOffset = convLength(sValue,maCurrState,'r'); + break; + } + case XML_STROKE_DASHARRAY: + { + if( aValueUtf8 == "none" ) + maCurrState.maDashArray.clear(); + else if( aValueUtf8 == "inherit" ) + maCurrState.maDashArray = maParentStates.back().maDashArray; + else + parseDashArray(aValueUtf8.getStr(), + maCurrState.maDashArray); + break; + } + case XML_STROKE_OPACITY: + if( aValueUtf8 == "inherit" ) + maCurrState.mnStrokeOpacity = maParentStates.back().mnStrokeOpacity; + else + maCurrState.mnStrokeOpacity = aValueUtf8.toDouble(); + break; + case XML_FILL: + { + const State& rParent( maParentStates.back() ); + parsePaint( sValue, + aValueUtf8.getStr(), + maCurrState.meFillType, + maCurrState.maFillColor, + maCurrState.maFillGradient, + rParent.meFillType, + rParent.maFillColor, + rParent.maFillGradient ); + break; + } + case XML_STROKE: + { + const State& rParent( maParentStates.back() ); + parsePaint( sValue, + aValueUtf8.getStr(), + maCurrState.meStrokeType, + maCurrState.maStrokeColor, + maCurrState.maStrokeGradient, + rParent.meStrokeType, + rParent.maStrokeColor, + rParent.maStrokeGradient ); + break; + } + case XML_TRANSFORM: + { + basegfx::B2DHomMatrix aTransform; + parseTransform(aValueUtf8.getStr(),aTransform); + maCurrState.maTransform = maCurrState.maTransform*aTransform; + break; + } + case XML_FONT_FAMILY: + maCurrState.maFontFamily=sValue; + break; + case XML_FONT_SIZE: + maCurrState.mnFontSize=convLength(sValue,maCurrState,'v'); + break; + case XML_FONT_STYLE: + parseFontStyle(maCurrState,sValue,aValueUtf8.getStr()); + break; + case XML_FONT_WEIGHT: + maCurrState.mnFontWeight=sValue.toDouble(); + break; + case XML_FONT_VARIANT: + parseFontVariant(maCurrState,sValue,aValueUtf8.getStr()); + break; + case XML_TEXT_ANCHOR: + parseTextAlign(maCurrState,aValueUtf8.getStr()); + break; + case XML_STOP_COLOR: + if( maGradientVector.empty() || + maGradientVector.back().maStops.empty() ) + break; + parseColor( aValueUtf8, + maGradientStopVector[ + maGradientVector.back().maStops.back()].maStopColor ); + break; + case XML_STOP_OPACITY: + if( maGradientVector.empty() || + maGradientVector.back().maStops.empty() ) + break; + parseOpacity( aValueUtf8, + maGradientStopVector[ + maGradientVector.back().maStops.back()].maStopColor ); + break; + default: + OSL_TRACE("unhandled token %s", getTokenName(nTokenId)); + break; + } + } + + void parseStyle( const rtl::OUString& sValue ) + { + // split individual style attributes + sal_Int32 nIndex=0, nDummyIndex=0; + rtl::OUString aCurrToken; + do + { + aCurrToken=sValue.getToken(0,';',nIndex); + + if( aCurrToken.getLength() ) + { + // split attrib & value + nDummyIndex=0; + rtl::OUString aCurrAttrib( + aCurrToken.getToken(0,':',nDummyIndex).trim()); + OSL_ASSERT(nDummyIndex!=-1); + nDummyIndex=0; + rtl::OUString aCurrValue( + aCurrToken.getToken(1,':',nDummyIndex).trim()); + OSL_ASSERT(nDummyIndex==-1); + + // recurse into normal attribute parsing + parseAttribute( getTokenId(aCurrAttrib), + aCurrValue ); + } + } + while( nIndex != -1 ); + } + + void parseFontStyle( State& io_rInitialState, + const rtl::OUString& rValue, + const char* sValue ) + { + if( strcmp(sValue,"inherit") != 0 ) + io_rInitialState.maFontStyle = rValue; + } + + void parseFontVariant( State& io_rInitialState, + const rtl::OUString& rValue, + const char* sValue ) + { + if( strcmp(sValue,"inherit") != 0 ) + io_rInitialState.maFontVariant = rValue; + } + + void parseTextAlign( State& io_rInitialState, + const char* sValue ) + { + if( strcmp(sValue,"start") == 0 ) + io_rInitialState.meTextAnchor = BEFORE; + else if( strcmp(sValue,"middle") == 0 ) + io_rInitialState.meTextAnchor = CENTER; + else if( strcmp(sValue,"end") == 0 ) + io_rInitialState.meTextAnchor = AFTER; + // keep current val for sValue == "inherit" + } + + void parsePaint( const rtl::OUString& rValue, + const char* sValue, + PaintType& rType, + ARGBColor& rColor, + Gradient& rGradient, + const PaintType& rInheritType, + const ARGBColor& rInheritColor, + const Gradient& rInheritGradient ) + { + std::pair<const char*,const char*> aPaintUri(NULL,NULL); + std::pair<ARGBColor,bool> aColor(maCurrState.maCurrentColor, + false); + if( strcmp(sValue,"none") == 0 ) + rType = NONE; + else if( strcmp(sValue,"currentColor") == 0 ) + { + rType = SOLID; + rColor = maCurrState.maCurrentColor; + } + else if( strcmp(sValue,"inherit") == 0) + { + rType = rInheritType; + rColor = rInheritColor; + rGradient = rInheritGradient; + } + else if( parsePaintUri(aPaintUri,aColor,sValue) ) + { + if( aPaintUri.first != aPaintUri.second ) + { + // assuming gradient. assumption does not hold generally + const char* pClosingBracket; + if( (pClosingBracket=strstr(sValue,")")) && rValue.getLength() > 5 ) + { + ElementRefMapType::iterator aRes; + if( (aRes=maGradientIdMap.find( + rValue.copy(aPaintUri.first-sValue, + aPaintUri.second-aPaintUri.first))) != maGradientIdMap.end() ) + { + rGradient = maGradientVector[aRes->second]; + rType = GRADIENT; + } + } + } + else if( aColor.second ) + { + rType = SOLID; + rColor = aColor.first; + } + else + { + rType = NONE; + } + } + else + { + rType = SOLID; + parseColor(sValue,rColor); + } + } + + sal_Int32 mnCurrStateId; + State maCurrState; + std::vector<State> maParentStates; + StatePool& mrStates; + StateMap& mrStateMap; + uno::Reference<xml::sax::XDocumentHandler> mxDocumentHandler; + std::vector< Gradient > maGradientVector; + std::vector< GradientStop > maGradientStopVector; + ElementRefMapType maGradientIdMap; + ElementRefMapType maStopIdMap; +}; + +/// Annotate svg styles with unique references to state pool +static void annotateStyles( StatePool& rStatePool, + StateMap& rStateMap, + const State& rInitialState, + const uno::Reference<xml::dom::XElement> xElem, + const uno::Reference<xml::sax::XDocumentHandler>& xDocHdl ) +{ + AnnotatingVisitor aVisitor(rStatePool,rStateMap,rInitialState,xDocHdl); + visitElements(aVisitor, xElem); +} + +struct ShapeWritingVisitor +{ + ShapeWritingVisitor(StatePool& /*rStatePool*/, + StateMap& rStateMap, + const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler) : + mrStateMap(rStateMap), + mxDocumentHandler(xDocumentHandler), + mnShapeNum(0) + {} + + void operator()( const uno::Reference<xml::dom::XElement>& ) + { + } + + void operator()( const uno::Reference<xml::dom::XElement>& xElem, + const uno::Reference<xml::dom::XNamedNodeMap>& xAttributes ) + { + rtl::Reference<SvXMLAttributeList> xAttrs( new SvXMLAttributeList() ); + uno::Reference<xml::sax::XAttributeList> xUnoAttrs( xAttrs.get() ); + + sal_Int32 nDummyIndex(0); + rtl::OUString sStyleId( + xElem->getAttribute( + USTR("internal-style-ref")).getToken( + 0,'$',nDummyIndex)); + StateMap::iterator pOrigState=mrStateMap.find( + sStyleId.toInt32()); + + if( pOrigState == mrStateMap.end() ) + return; // non-exportable element, e.g. linearGradient + + maCurrState = pOrigState->second; + + const sal_Int32 nTokenId(getTokenId(xElem->getNodeName())); + switch(nTokenId) + { + case XML_LINE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double x1=0.0,y1=0.0,x2=0.0,y2=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X1: + x1= convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_X2: + x2 = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y1: + y1 = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_Y2: + y2 = convLength(sAttributeValue,maCurrState,'v'); + break; + default: + // skip + break; + } + } + + rtl::OUString sLinePath = USTR("M")+rtl::OUString::valueOf(x1)+USTR(",") + +rtl::OUString::valueOf(y1)+USTR("L")+rtl::OUString::valueOf(x2)+USTR(",") + +rtl::OUString::valueOf(y2); + basegfx::B2DPolyPolygon aPoly; + basegfx::tools::importFromSvgD(aPoly, sLinePath); + + writePathShape(xAttrs, + xUnoAttrs, + xElem, + sStyleId, + basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_POLYGON: + case XML_POLYLINE: + { + rtl::OUString sPoints = xElem->hasAttribute(USTR("points")) ? xElem->getAttribute(USTR("points")) : USTR(""); + basegfx::B2DPolygon aPoly; + basegfx::tools::importFromSvgPoints(aPoly, sPoints); + if( nTokenId == XML_POLYGON || maCurrState.meFillType != NONE ) + aPoly.setClosed(true); + + writePathShape(xAttrs, + xUnoAttrs, + xElem, + sStyleId, + basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_RECT: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + bool bRxSeen=false, bRySeen=false; + double x=0.0,y=0.0,width=0.0,height=0.0,rx=0.0,ry=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X: + x = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y: + y = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_WIDTH: + width = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_HEIGHT: + height = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_RX: + rx = convLength(sAttributeValue,maCurrState,'h'); + bRxSeen=true; + break; + case XML_RY: + ry = convLength(sAttributeValue,maCurrState,'v'); + bRySeen=true; + break; + default: + // skip + break; + } + } + + if( bRxSeen && !bRySeen ) + ry = rx; + else if( bRySeen && !bRxSeen ) + rx = ry; + + basegfx::B2DPolygon aPoly; + aPoly = basegfx::tools::createPolygonFromRect( + basegfx::B2DRange(x,y,x+width,y+height), + rx/width, ry/height ); + + writePathShape(xAttrs, + xUnoAttrs, + xElem, + sStyleId, + basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_PATH: + { + rtl::OUString sPath = xElem->hasAttribute(USTR("d")) ? xElem->getAttribute(USTR("d")) : USTR(""); + basegfx::B2DPolyPolygon aPoly; + basegfx::tools::importFromSvgD(aPoly, sPath); + + writePathShape(xAttrs, + xUnoAttrs, + xElem, + sStyleId, + aPoly); + break; + } + case XML_CIRCLE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double cx=0.0,cy=0.0,r=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_CX: + cx = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_CY: + cy = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_R: + r = convLength(sAttributeValue,maCurrState,'r'); + default: + // skip + break; + } + } + + writeEllipseShape(xAttrs, + xUnoAttrs, + xElem, + sStyleId, + basegfx::B2DEllipse(basegfx::B2DPoint(cx, cy), basegfx::B2DTuple(r,r))); + break; + } + case XML_ELLIPSE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double cx=0.0,cy=0.0,rx=0.0, ry=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_CX: + cx = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_CY: + cy = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_RX: + rx = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_RY: + ry = convLength(sAttributeValue,maCurrState,'v'); + default: + // skip + break; + } + } + + writeEllipseShape(xAttrs, + xUnoAttrs, + xElem, + sStyleId, + basegfx::B2DEllipse(basegfx::B2DPoint(cx, cy), basegfx::B2DTuple(rx,ry))); + break; + } + case XML_IMAGE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double x=0.0,y=0.0,width=0.0,height=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X: + x = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y: + y = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_WIDTH: + width = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_HEIGHT: + height = convLength(sAttributeValue,maCurrState,'v'); + break; + default: + // skip + break; + } + } + + rtl::OUString sValue = xElem->hasAttribute(USTR("href")) ? xElem->getAttribute(USTR("href")) : USTR(""); + rtl::OString aValueUtf8( sValue.getStr(), sValue.getLength(), RTL_TEXTENCODING_UTF8 ); + std::string sLinkValue; + parseXlinkHref(aValueUtf8.getStr(), sLinkValue); + + if (!sLinkValue.empty()) + writeBinaryData(xAttrs, xUnoAttrs, xElem, basegfx::B2DRange(x,y,x+width,y+height), sLinkValue); + break; + } + case XML_TEXT: + { + // collect text from all TEXT_NODE children into sText + rtl::OUStringBuffer sText; + visitChildren(boost::bind( + (rtl::OUStringBuffer& (rtl::OUStringBuffer::*)(const sal_Unicode* str))&rtl::OUStringBuffer::append, + boost::ref(sText), + boost::bind(&xml::dom::XNode::getNodeValue, + _1)), + xElem, + xml::dom::NodeType_TEXT_NODE); + + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double x=0.0,y=0.0,width=0.0,height=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X: + x = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y: + y = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_WIDTH: + width = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_HEIGHT: + height = convLength(sAttributeValue,maCurrState,'v'); + break; + default: + // skip + break; + } + } + + // actually export text + xAttrs->Clear(); + + // some heuristic attempts to have text output + // baseline-relative + y -= 2.0*maCurrState.mnFontSize/3.0; + + // extract basic transformations out of CTM + basegfx::B2DTuple aScale, aTranslate; + double fRotate, fShearX; + ::rtl::OUString sTransformValue; + if (maCurrState.maCTM.decompose(aScale, aTranslate, fRotate, fShearX)) + { + rtl::OUString sTransform; + x += aTranslate.getX(); + y += aTranslate.getY(); + + sTransform += + USTR("scale(") + + rtl::OUString::valueOf(aScale.getX()) + + USTR(", ") + + rtl::OUString::valueOf(aScale.getX()) + + USTR(")"); + + if( fRotate ) + sTransform += USTR(" rotate(") + rtl::OUString::valueOf(fRotate*180.0/M_PI) + USTR(")"); + + if( fShearX ) + sTransform += USTR(" skewX(") + rtl::OUString::valueOf(fShearX*180.0/M_PI) + USTR(")"); + } + + xAttrs->AddAttribute( USTR( "svg:x" ), rtl::OUString::valueOf(pt2mm(x))+USTR("mm")); + xAttrs->AddAttribute( USTR( "svg:y" ), rtl::OUString::valueOf(pt2mm(y))+USTR("mm")); + xAttrs->AddAttribute( USTR( "draw:style-name" ), USTR("svggraphicstyle")+sStyleId ); + + mxDocumentHandler->startElement(USTR("draw:frame"),xUnoAttrs); + + xAttrs->Clear(); + mxDocumentHandler->startElement(USTR("draw:text-box"),xUnoAttrs); + xAttrs->AddAttribute( USTR( "text:style-name" ), USTR("svgparagraphstyle")+sStyleId); + mxDocumentHandler->startElement(USTR("text:p"),xUnoAttrs); + + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "text:style-name" ), USTR("svgtextstyle")+sStyleId); + mxDocumentHandler->startElement(USTR("text:span"),xUnoAttrs); + + xAttrs->Clear(); + mxDocumentHandler->characters(sText.makeStringAndClear()); + mxDocumentHandler->endElement(USTR("text:span")); + mxDocumentHandler->endElement(USTR("text:p")); + mxDocumentHandler->endElement(USTR("draw:text-box")); + mxDocumentHandler->endElement(USTR("draw:frame")); + break; + } + } + } + + void push() + {} + + void pop() + {} + + void writeBinaryData( rtl::Reference<SvXMLAttributeList>& xAttrs, + const uno::Reference<xml::sax::XAttributeList>& xUnoAttrs, + const uno::Reference<xml::dom::XElement>& /* xElem */, + const basegfx::B2DRange& rShapeBounds, + const std::string& data) + { + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "svg:x" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getMinX()))+USTR("mm")); + xAttrs->AddAttribute( USTR( "svg:y" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getMinY()))+USTR("mm")); + xAttrs->AddAttribute( USTR( "svg:width" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getWidth()))+USTR("mm")); + xAttrs->AddAttribute( USTR( "svg:height" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getHeight()))+USTR("mm")); + + mxDocumentHandler->startElement(USTR("draw:frame"),xUnoAttrs); + + xAttrs->Clear(); + mxDocumentHandler->startElement(USTR("draw:image"),xUnoAttrs); + + mxDocumentHandler->startElement(USTR("office:binary-data"),xUnoAttrs); + + mxDocumentHandler->characters(rtl::OUString::createFromAscii(data.c_str())); + + mxDocumentHandler->endElement(USTR("office:binary-data")); + + mxDocumentHandler->endElement(USTR("draw:image")); + + mxDocumentHandler->endElement(USTR("draw:frame")); + } + + + void writeTransformAttribute(const basegfx::B2DHomMatrix rMatrix, rtl::Reference<SvXMLAttributeList>& xAttrs) + { + basegfx::B2DTuple rScale, rTranslate; + double rRotate, rShearX; + ::rtl::OUString sTransformValue; + if (!rMatrix.decompose(rScale, rTranslate, rRotate, rShearX)) + return; + if (rScale.getX() != 1.0 || rScale.getY() != 1.0) + sTransformValue += USTR("scale(")+::rtl::OUString::valueOf(rScale.getX())+USTR(" ") + +::rtl::OUString::valueOf(rScale.getY())+USTR(") "); + if (rTranslate.getX() != 0.0f || rTranslate.getY() != 0.0f) + sTransformValue += USTR("translate(")+::rtl::OUString::valueOf(rTranslate.getX()/100.0f)+USTR("mm ") + +::rtl::OUString::valueOf(rTranslate.getY()/100.0f)+USTR("mm) "); + if (rRotate != 0.0f) + sTransformValue += USTR("rotate(")+::rtl::OUString::valueOf(rRotate)+USTR(") "); + + if (rShearX != 0.0f) + sTransformValue += USTR("skewX(")+::rtl::OUString::valueOf(rShearX)+USTR(") "); + if (!sTransformValue.getLength()) + return; + xAttrs->AddAttribute( USTR("draw:transform"), sTransformValue); + } + + void writeEllipseShape( rtl::Reference<SvXMLAttributeList>& xAttrs, + const uno::Reference<xml::sax::XAttributeList>& xUnoAttrs, + const uno::Reference<xml::dom::XElement>& xElem, + const rtl::OUString& rStyleId, + const basegfx::B2DEllipse& rEllipse) + { + State aState = maCurrState; + rtl::OUString aStyleId(rStyleId); + + xAttrs->Clear(); + + basegfx::B2DPolygon aPoly = basegfx::tools::createPolygonFromEllipse(rEllipse.getB2DEllipseCenter(), + rEllipse.getB2DEllipseRadius().getX(), rEllipse.getB2DEllipseRadius().getY()); + writePathShape(xAttrs, xUnoAttrs, xElem, rStyleId, basegfx::B2DPolyPolygon(aPoly)); + + } + + void writePathShape( rtl::Reference<SvXMLAttributeList>& xAttrs, + const uno::Reference<xml::sax::XAttributeList>& xUnoAttrs, + const uno::Reference<xml::dom::XElement>& xElem, + const rtl::OUString& rStyleId, + const basegfx::B2DPolyPolygon& rPoly ) + { + // we might need to split up polypolygon into multiple path + // shapes (e.g. when emulating line stroking) + std::vector<basegfx::B2DPolyPolygon> aPolys(1,rPoly); + State aState = maCurrState; + rtl::OUString aStyleId(rStyleId); + + xAttrs->Clear(); + + OSL_TRACE("writePath - the CTM is: %f %f %f %f %f %f", + maCurrState.maCTM.get(0,0), + maCurrState.maCTM.get(0,1), + maCurrState.maCTM.get(0,2), + maCurrState.maCTM.get(1,0), + maCurrState.maCTM.get(1,1), + maCurrState.maCTM.get(1,2)); + + if( aState.meStrokeType != NONE && aState.maDashArray.size() ) + { + // ODF dashing is severly borked - generate filled polygon instead + aPolys.clear(); + for( sal_uInt32 i=0; i<rPoly.count(); ++i ) + { + aPolys.push_back( + basegfx::tools::stripNeutralPolygons( + basegfx::tools::prepareForPolygonOperation( + basegfx::tools::createAreaGeometry( + rPoly.getB2DPolygon(i), + aState.mnStrokeWidth/2.0, + aState.meLineJoin)))); + // TODO(F2): line ends + } + + sal_Int32 nDummyIndex(0); + aStyleId = xElem->getAttribute( + USTR("internal-style-ref")).getToken(1,'$',nDummyIndex); + StateMap::iterator pAlternateState=mrStateMap.find(aStyleId.toInt32()); + OSL_ASSERT(pAlternateState != mrStateMap.end()); + aState = pAlternateState->second; + OSL_ENSURE( pAlternateState == mrStateMap.end(), + "Doh - where's my alternate style entry?!" ); + } + + // TODO(F2): separate out shear, rotate etc. + // apply transformation to polygon, to keep draw + // import in 100th mm + std::for_each(aPolys.begin(),aPolys.end(), + boost::bind(&basegfx::B2DPolyPolygon::transform, + _1,boost::cref(aState.maCTM))); + + for( sal_uInt32 i=0; i<aPolys.size(); ++i ) + { + const basegfx::B2DRange aBounds( + aPolys[i].areControlPointsUsed() ? + basegfx::tools::getRange( + basegfx::tools::adaptiveSubdivideByAngle(aPolys[i])) : + basegfx::tools::getRange(aPolys[i])); + fillShapeProperties(xAttrs, + xElem, + aBounds, + USTR("svggraphicstyle")+aStyleId); + + // force path coordinates to 100th millimeter, after + // putting polygon data at origin (odf viewbox + // calculations largely untested codepaths, as OOo always + // writes "0 0 w h" viewboxes) + basegfx::B2DHomMatrix aNormalize; + aNormalize.translate(-aBounds.getMinX(),-aBounds.getMinY()); + aNormalize.scale(2540.0/72.0,2540.0/72.0); + aPolys[i].transform(aNormalize); + + xAttrs->AddAttribute( USTR( "svg:d" ), basegfx::tools::exportToSvgD( + aPolys[i], + false, // no relative coords. causes rounding errors + false )); // no quad bezier detection. crashes older versions. + mxDocumentHandler->startElement(USTR("draw:path"), + xUnoAttrs); + mxDocumentHandler->endElement(USTR("draw:path")); + } + } + + void fillShapeProperties( rtl::Reference<SvXMLAttributeList>& xAttrs, + const uno::Reference<xml::dom::XElement>& /* xElem */, + const basegfx::B2DRange& rShapeBounds, + const rtl::OUString& rStyleName ) + { + xAttrs->AddAttribute( USTR( "draw:z-index" ), rtl::OUString::valueOf( mnShapeNum++ )); + xAttrs->AddAttribute( USTR( "draw:style-name" ), rStyleName); + xAttrs->AddAttribute( USTR( "svg:width" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getWidth()))+USTR("mm")); + xAttrs->AddAttribute( USTR( "svg:height" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getHeight()))+USTR("mm")); + + // OOo expects the viewbox to be in 100th of mm + xAttrs->AddAttribute( USTR( "svg:viewBox" ), + USTR("0 0 ") + + rtl::OUString::valueOf( + basegfx::fround(pt100thmm(rShapeBounds.getWidth())) ) + + USTR(" ") + + rtl::OUString::valueOf( + basegfx::fround(pt100thmm(rShapeBounds.getHeight())) )); + + // TODO(F1): decompose transformation in calling code, and use + // transform attribute here + // writeTranslate(maCurrState.maCTM, xAttrs); + xAttrs->AddAttribute( USTR( "svg:x" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getMinX()))+USTR("mm")); + xAttrs->AddAttribute( USTR( "svg:y" ), rtl::OUString::valueOf(pt2mm(rShapeBounds.getMinY()))+USTR("mm")); + } + + State maCurrState; + StateMap& mrStateMap; + uno::Reference<xml::sax::XDocumentHandler> mxDocumentHandler; + sal_Int32 mnShapeNum; +}; + +/// Write out shapes from DOM tree +static void writeShapes( StatePool& rStatePool, + StateMap& rStateMap, + const uno::Reference<xml::dom::XElement> xElem, + const uno::Reference<xml::sax::XDocumentHandler>& xDocHdl ) +{ + ShapeWritingVisitor aVisitor(rStatePool,rStateMap,xDocHdl); + visitElements(aVisitor, xElem); +} + +#ifdef VERBOSE +struct DumpingVisitor +{ + void operator()( const uno::Reference<xml::dom::XElement>& xElem ) + { + OSL_TRACE("name: %s", + rtl::OUStringToOString( + xElem->getTagName(), + RTL_TEXTENCODING_UTF8 ).getStr()); + } + + void operator()( const uno::Reference<xml::dom::XElement>& xElem, + const uno::Reference<xml::dom::XNamedNodeMap>& xAttributes ) + { + OSL_TRACE("name: %s", + rtl::OUStringToOString( + xElem->getTagName(), + RTL_TEXTENCODING_UTF8 ).getStr()); + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + OSL_TRACE(" %s=%s", + rtl::OUStringToOString( + xAttributes->item(i)->getNodeName(), + RTL_TEXTENCODING_UTF8 ).getStr(), + rtl::OUStringToOString( + xAttributes->item(i)->getNodeValue(), + RTL_TEXTENCODING_UTF8 ).getStr()); + } + } + + void push() {} + void pop() {} +}; + +static void dumpTree( const uno::Reference<xml::dom::XElement> xElem ) +{ + DumpingVisitor aVisitor; + visitElements(aVisitor, xElem); +} +#endif + +} // namespace + + +SVGReader::SVGReader(const uno::Reference<lang::XMultiServiceFactory>& xServiceFactory, + const uno::Reference<io::XInputStream>& xInputStream, + const uno::Reference<xml::sax::XDocumentHandler>& xDocumentHandler) : + m_xServiceFactory( xServiceFactory ), + m_xInputStream( xInputStream ), + m_xDocumentHandler( xDocumentHandler ) +{ +} + +sal_Bool SVGReader::parseAndConvert() +{ + uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder( + m_xServiceFactory->createInstance( + rtl::OUString::createFromAscii("com.sun.star.xml.dom.DocumentBuilder")), uno::UNO_QUERY_THROW ); + + uno::Reference<xml::dom::XDocument> xDom( + xDomBuilder->parse(m_xInputStream), + uno::UNO_QUERY_THROW ); + + uno::Reference<xml::dom::XElement> xDocElem( xDom->getDocumentElement(), + uno::UNO_QUERY_THROW ); + + // the root state for svg document + State aInitialState; + + ///////////////////////////////////////////////////////////////// + // doc boilerplate + ///////////////////////////////////////////////////////////////// + + m_xDocumentHandler->startDocument(); + + // get the document dimensions + + // if the "width" and "height" attributes are missing, inkscape fakes + // A4 portrait for. Let's do the same. + if (!xDocElem->hasAttribute(USTR("width"))) + xDocElem->setAttribute(USTR("width"), USTR("210mm")); + if (!xDocElem->hasAttribute(USTR("height"))) + xDocElem->setAttribute(USTR("height"), USTR("297mm")); + + double fViewPortWidth( pt2mm(convLength(xDocElem->getAttribute(USTR("width")),aInitialState,'h')) ); + double fViewPortHeight( pt2mm(convLength(xDocElem->getAttribute(USTR("height")),aInitialState,'v')) ); + + // document prolog + rtl::Reference<SvXMLAttributeList> xAttrs( new SvXMLAttributeList() ); + uno::Reference<xml::sax::XAttributeList> xUnoAttrs( xAttrs.get() ); + + xAttrs->AddAttribute( USTR( "xmlns:office" ), USTR( OASIS_STR "office:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:style" ), USTR( OASIS_STR "style:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:text" ), USTR( OASIS_STR "text:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:svg" ), USTR( OASIS_STR "svg-compatible:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:table" ), USTR( OASIS_STR "table:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:draw" ), USTR( OASIS_STR "drawing:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:fo" ), USTR( OASIS_STR "xsl-fo-compatible:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:xlink" ), USTR( "http://www.w3.org/1999/xlink" )); + xAttrs->AddAttribute( USTR( "xmlns:dc" ), USTR( "http://purl.org/dc/elements/1.1/" )); + xAttrs->AddAttribute( USTR( "xmlns:number" ), USTR( OASIS_STR "datastyle:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:presentation" ), USTR( OASIS_STR "presentation:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:math" ), USTR( "http://www.w3.org/1998/Math/MathML" )); + xAttrs->AddAttribute( USTR( "xmlns:form" ), USTR( OASIS_STR "form:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:script" ), USTR( OASIS_STR "script:1.0" )); + xAttrs->AddAttribute( USTR( "xmlns:dom" ), USTR( "http://www.w3.org/2001/xml-events" )); + xAttrs->AddAttribute( USTR( "xmlns:xforms" ), USTR( "http://www.w3.org/2002/xforms" )); + xAttrs->AddAttribute( USTR( "xmlns:xsd" ), USTR( "http://www.w3.org/2001/XMLSchema" )); + xAttrs->AddAttribute( USTR( "xmlns:xsi" ), USTR( "http://www.w3.org/2001/XMLSchema-instance" )); + xAttrs->AddAttribute( USTR( "office:version" ), USTR( "1.0" )); + xAttrs->AddAttribute( USTR( "office:mimetype" ), USTR( "application/vnd.oasis.opendocument.graphics" )); + + m_xDocumentHandler->startElement( USTR("office:document"), xUnoAttrs ); + + xAttrs->Clear(); + + m_xDocumentHandler->startElement( USTR("office:settings"), xUnoAttrs); + + xAttrs->AddAttribute( USTR( "config:name" ), USTR( "ooo:view-settings" )); + m_xDocumentHandler->startElement( USTR("config:config-item-set"), xUnoAttrs); + + xAttrs->Clear(); + + xAttrs->AddAttribute( USTR( "config:name" ), USTR( "VisibleAreaTop" )); + xAttrs->AddAttribute( USTR( "config:type" ), USTR( "int" )); + m_xDocumentHandler->startElement( USTR( "config:config-item" ), xUnoAttrs); + + m_xDocumentHandler->characters( USTR( "0" )); + + m_xDocumentHandler->endElement( USTR( "config:config-item" )); + + xAttrs->Clear(); + + xAttrs->AddAttribute( USTR( "config:name" ), USTR( "VisibleAreaLeft" )); + xAttrs->AddAttribute( USTR( "config:type" ), USTR( "int" )); + m_xDocumentHandler->startElement( USTR( "config:config-item" ), xUnoAttrs); + + m_xDocumentHandler->characters( USTR( "0" )); + + m_xDocumentHandler->endElement( USTR( "config:config-item" )); + + xAttrs->Clear(); + + xAttrs->AddAttribute( USTR( "config:name" ), USTR( "VisibleAreaWidth" )); + xAttrs->AddAttribute( USTR( "config:type" ), USTR( "int" )); + m_xDocumentHandler->startElement( USTR( "config:config-item" ), xUnoAttrs); + + sal_Int64 iWidth = sal_Int64(fViewPortWidth); + m_xDocumentHandler->characters( ::rtl::OUString::valueOf(iWidth) ); + + m_xDocumentHandler->endElement( USTR( "config:config-item" )); + + xAttrs->Clear(); + + xAttrs->AddAttribute( USTR( "config:name" ), USTR( "VisibleAreaHeight" )); + xAttrs->AddAttribute( USTR( "config:type" ), USTR( "int" )); + m_xDocumentHandler->startElement( USTR( "config:config-item" ), xUnoAttrs); + + sal_Int64 iHeight = sal_Int64(fViewPortHeight); + m_xDocumentHandler->characters( ::rtl::OUString::valueOf(iHeight) ); + + m_xDocumentHandler->endElement( USTR( "config:config-item" )); + + m_xDocumentHandler->endElement( USTR( "config:config-item-set" )); + + m_xDocumentHandler->endElement( USTR( "office:settings" )); + + xAttrs->Clear(); + + m_xDocumentHandler->startElement( USTR("office:automatic-styles"), + xUnoAttrs ); + + xAttrs->AddAttribute( USTR( "style:name" ), USTR("pagelayout1")); + m_xDocumentHandler->startElement( USTR("style:page-layout"), + xUnoAttrs ); + // TODO(Q3): this is super-ugly. In-place container come to mind. + xAttrs->Clear(); + + // make page viewport-width times viewport-height mm large - add + // 5% border at every side + xAttrs->AddAttribute( USTR( "fo:margin-top" ), USTR("0mm")); + xAttrs->AddAttribute( USTR( "fo:margin-bottom" ), USTR("0mm")); + xAttrs->AddAttribute( USTR( "fo:margin-left" ), USTR("0mm")); + xAttrs->AddAttribute( USTR( "fo:margin-right" ), USTR("0mm")); + xAttrs->AddAttribute( USTR( "fo:page-width" ), rtl::OUString::valueOf(fViewPortWidth)+USTR("mm")); + xAttrs->AddAttribute( USTR( "fo:page-height" ), rtl::OUString::valueOf(fViewPortHeight)+USTR("mm")); + xAttrs->AddAttribute( USTR( "style:print-orientation" ), + fViewPortWidth > fViewPortHeight ? + USTR("landscape") : + USTR("portrait")); + m_xDocumentHandler->startElement( USTR("style:page-layout-properties"), + xUnoAttrs ); + m_xDocumentHandler->endElement( USTR("style:page-layout-properties") ); + m_xDocumentHandler->endElement( USTR("style:page-layout") ); + + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "style:name" ), USTR("pagestyle1")); + xAttrs->AddAttribute( USTR( "style:family" ), USTR("drawing-page")); + m_xDocumentHandler->startElement( USTR("style:style"), + xUnoAttrs ); + + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "draw:background-size" ), USTR("border")); + xAttrs->AddAttribute( USTR( "draw:fill" ), USTR("none")); + m_xDocumentHandler->startElement( USTR("style:drawing-page-properties"), + xUnoAttrs ); + m_xDocumentHandler->endElement( USTR("style:drawing-page-properties") ); + m_xDocumentHandler->endElement( USTR("style:style") ); + + StatePool aStatePool; + StateMap aStateMap; + annotateStyles(aStatePool,aStateMap,aInitialState, + xDocElem,m_xDocumentHandler); + +#ifdef VERBOSE + dumpTree(xDocElem); +#endif + + m_xDocumentHandler->endElement( USTR("office:automatic-styles") ); + + //////////////////////////////////////////////////////////////////// + + xAttrs->Clear(); + m_xDocumentHandler->startElement( USTR("office:styles"), + xUnoAttrs); + m_xDocumentHandler->endElement( USTR("office:styles") ); + + //////////////////////////////////////////////////////////////////// + + m_xDocumentHandler->startElement( USTR("office:master-styles"), + xUnoAttrs ); + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "style:name" ), USTR("Default")); + xAttrs->AddAttribute( USTR( "style:page-layout-name" ), USTR("pagelayout1")); + xAttrs->AddAttribute( USTR( "draw:style-name" ), USTR("pagestyle1")); + m_xDocumentHandler->startElement( USTR("style:master-page"), + xUnoAttrs ); + m_xDocumentHandler->endElement( USTR("style:master-page") ); + + m_xDocumentHandler->endElement( USTR("office:master-styles") ); + + //////////////////////////////////////////////////////////////////// + + xAttrs->Clear(); + m_xDocumentHandler->startElement( USTR("office:body"), + xUnoAttrs ); + m_xDocumentHandler->startElement( USTR("office:drawing"), + xUnoAttrs ); + + xAttrs->Clear(); + xAttrs->AddAttribute( USTR( "draw:master-page-name" ), USTR("Default")); + xAttrs->AddAttribute( USTR( "draw:style-name" ), USTR("pagestyle1")); + m_xDocumentHandler->startElement(USTR("draw:page"), + xUnoAttrs); + + // write out all shapes + writeShapes(aStatePool, + aStateMap, + xDocElem, + m_xDocumentHandler); + + m_xDocumentHandler->endElement( USTR("draw:page") ); + m_xDocumentHandler->endElement( USTR("office:drawing") ); + m_xDocumentHandler->endElement( USTR("office:body") ); + m_xDocumentHandler->endElement( USTR("office:document") ); + m_xDocumentHandler->endDocument(); + + return sal_True; +} + +/////////////////////////////////////////////////////////////// + +struct ShapeRenderingVisitor +{ + ShapeRenderingVisitor(StatePool& /*rStatePool*/, + StateMap& rStateMap, + OutputDevice& rOutDev, + const std::vector< Gradient >& rGradientVector, + const std::vector< GradientStop >& rGradientStopVector) : + mrStateMap(rStateMap), + mrOutDev(rOutDev), + mrGradientVector(rGradientVector), + mrGradientStopVector(rGradientStopVector) + {} + + void operator()( const uno::Reference<xml::dom::XElement>& ) + { + } + + void operator()( const uno::Reference<xml::dom::XElement>& xElem, + const uno::Reference<xml::dom::XNamedNodeMap>& xAttributes ) + { + sal_Int32 nDummyIndex(0); + rtl::OUString sStyleId( + xElem->getAttribute( + USTR("internal-style-ref")).getToken( + 0,'$',nDummyIndex)); + StateMap::iterator pOrigState=mrStateMap.find( + sStyleId.toInt32()); + + if( pOrigState == mrStateMap.end() ) + return; // non-exportable element, e.g. linearGradient + + maCurrState = pOrigState->second; + + const sal_Int32 nTokenId(getTokenId(xElem->getNodeName())); + switch(nTokenId) + { + case XML_LINE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double x1=0.0,y1=0.0,x2=0.0,y2=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X1: + x1= convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_X2: + x2 = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y1: + y1 = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_Y2: + y2 = convLength(sAttributeValue,maCurrState,'v'); + break; + default: + // skip + break; + } + } + + basegfx::B2DPolygon aPoly; + aPoly.append(basegfx::B2DPoint(x1,y1)); + aPoly.append(basegfx::B2DPoint(x2,y2)); + + renderPathShape(basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_POLYGON: + case XML_POLYLINE: + { + rtl::OUString sPoints = xElem->hasAttribute(USTR("points")) ? xElem->getAttribute(USTR("points")) : USTR(""); + basegfx::B2DPolygon aPoly; + basegfx::tools::importFromSvgPoints(aPoly, sPoints); + if( nTokenId == XML_POLYGON || maCurrState.meFillType != NONE ) + aPoly.setClosed(true); + + renderPathShape(basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_RECT: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + bool bRxSeen=false, bRySeen=false; + double x=0.0,y=0.0,width=0.0,height=0.0,rx=0.0,ry=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X: + x = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y: + y = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_WIDTH: + width = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_HEIGHT: + height = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_RX: + rx = convLength(sAttributeValue,maCurrState,'h'); + bRxSeen=true; + break; + case XML_RY: + ry = convLength(sAttributeValue,maCurrState,'v'); + bRySeen=true; + break; + default: + // skip + break; + } + } + + if( bRxSeen && !bRySeen ) + ry = rx; + else if( bRySeen && !bRxSeen ) + rx = ry; + + basegfx::B2DPolygon aPoly; + aPoly = basegfx::tools::createPolygonFromRect( + basegfx::B2DRange(x,y,x+width,y+height), + rx, ry ); + + renderPathShape(basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_PATH: + { + rtl::OUString sPath = xElem->hasAttribute(USTR("d")) ? xElem->getAttribute(USTR("d")) : USTR(""); + basegfx::B2DPolyPolygon aPoly; + basegfx::tools::importFromSvgD(aPoly, sPath); + + renderPathShape(aPoly); + break; + } + case XML_CIRCLE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double cx=0.0,cy=0.0,r=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_CX: + cx = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_CY: + cy = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_R: + r = convLength(sAttributeValue,maCurrState,'o'); + default: + // skip + break; + } + } + + basegfx::B2DEllipse aEllipse(basegfx::B2DPoint(cx, cy), basegfx::B2DTuple(r,r)); + basegfx::B2DPolygon aPoly = basegfx::tools::createPolygonFromEllipse( + aEllipse.getB2DEllipseCenter(), + aEllipse.getB2DEllipseRadius().getX(), + aEllipse.getB2DEllipseRadius().getY()); + + renderPathShape(basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_ELLIPSE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double cx=0.0,cy=0.0,rx=0.0, ry=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_CX: + cx = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_CY: + cy = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_RX: + rx = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_RY: + ry = convLength(sAttributeValue,maCurrState,'v'); + default: + // skip + break; + } + } + + basegfx::B2DEllipse aEllipse(basegfx::B2DPoint(cx, cy), basegfx::B2DTuple(rx,ry)); + basegfx::B2DPolygon aPoly = basegfx::tools::createPolygonFromEllipse( + aEllipse.getB2DEllipseCenter(), + aEllipse.getB2DEllipseRadius().getX(), + aEllipse.getB2DEllipseRadius().getY()); + + renderPathShape(basegfx::B2DPolyPolygon(aPoly)); + break; + } + case XML_IMAGE: + { + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double x=0.0,y=0.0,width=0.0,height=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X: + x = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y: + y = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_WIDTH: + width = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_HEIGHT: + height = convLength(sAttributeValue,maCurrState,'v'); + break; + default: + // skip + break; + } + } + + rtl::OUString sValue = xElem->hasAttribute(USTR("href")) ? xElem->getAttribute(USTR("href")) : USTR(""); + rtl::OString aValueUtf8( sValue.getStr(), sValue.getLength(), RTL_TEXTENCODING_UTF8 ); + std::string sLinkValue; + parseXlinkHref(aValueUtf8.getStr(), sLinkValue); + + if (!sLinkValue.empty()) + { + // <- blatant copy from svx/source/xml/xmlgrhlp.cxx + Graphic aGraphic; + + uno::Sequence<sal_Int8> aData; + SvXMLUnitConverter::decodeBase64(aData, + rtl::OUString::createFromAscii(sLinkValue.c_str())); + SvMemoryStream aSrc(aData.getArray(), + aData.getLength(), + STREAM_READ); + USHORT nFormat = GRFILTER_FORMAT_DONTKNOW; + USHORT pDeterminedFormat = GRFILTER_FORMAT_DONTKNOW; + GraphicFilter::GetGraphicFilter()->ImportGraphic( aGraphic, String(), aSrc ,nFormat,&pDeterminedFormat ); + + if (pDeterminedFormat == GRFILTER_FORMAT_DONTKNOW) + { + //Read the first two byte to check whether it is a gzipped stream, is so it may be in wmz or emz format + //unzip them and try again + + BYTE sFirstBytes[ 2 ]; + + aSrc.Seek( STREAM_SEEK_TO_END ); + ULONG nStreamLen = aSrc.Tell(); + aSrc.Seek( 0 ); + + if ( !nStreamLen ) + { + SvLockBytes* pLockBytes = aSrc.GetLockBytes(); + if ( pLockBytes ) + pLockBytes->SetSynchronMode( TRUE ); + + aSrc.Seek( STREAM_SEEK_TO_END ); + nStreamLen = aSrc.Tell(); + aSrc.Seek( 0 ); + } + if( nStreamLen >= 2 ) + { + //read two byte + aSrc.Read( sFirstBytes, 2 ); + + if( sFirstBytes[0] == 0x1f && sFirstBytes[1] == 0x8b ) + { + SvMemoryStream* pDest = new SvMemoryStream; + ZCodec aZCodec( 0x8000, 0x8000 ); + aZCodec.BeginCompression(ZCODEC_GZ_LIB); + aSrc.Seek( 0 ); + aZCodec.Decompress( aSrc, *pDest ); + + if (aZCodec.EndCompression() && pDest ) + { + pDest->Seek( STREAM_SEEK_TO_END ); + ULONG nStreamLen_ = pDest->Tell(); + if (nStreamLen_) + { + pDest->Seek(0L); + GraphicFilter::GetGraphicFilter()->ImportGraphic( aGraphic, String(), *pDest ,nFormat,&pDeterminedFormat ); + } + } + delete pDest; + } + } + } + // -> blatant copy from svx/source/xml/xmlgrhlp.cxx + + const Rectangle aBounds( + Point(basegfx::fround(pt100thmm(x)), + basegfx::fround(pt100thmm(y))), + Size(basegfx::fround(pt100thmm(width)), + basegfx::fround(pt100thmm(height)))); + aGraphic.Draw(&mrOutDev, + aBounds.TopLeft(), + aBounds.GetSize()); + maBounds.Union(aBounds); + } + break; + } + case XML_TEXT: + { + // collect text from all TEXT_NODE children into sText + rtl::OUStringBuffer sText; + visitChildren(boost::bind( + (rtl::OUStringBuffer& (rtl::OUStringBuffer::*)(const sal_Unicode* str))&rtl::OUStringBuffer::append, + boost::ref(sText), + boost::bind(&xml::dom::XNode::getNodeValue, + _1)), + xElem, + xml::dom::NodeType_TEXT_NODE); + + // collect attributes + const sal_Int32 nNumAttrs( xAttributes->getLength() ); + rtl::OUString sAttributeValue; + double x=0.0,y=0.0,width=0.0,height=0.0; + for( sal_Int32 i=0; i<nNumAttrs; ++i ) + { + sAttributeValue = xAttributes->item(i)->getNodeValue(); + const sal_Int32 nAttribId( + getTokenId(xAttributes->item(i)->getNodeName())); + switch(nAttribId) + { + case XML_X: + x = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_Y: + y = convLength(sAttributeValue,maCurrState,'v'); + break; + case XML_WIDTH: + width = convLength(sAttributeValue,maCurrState,'h'); + break; + case XML_HEIGHT: + height = convLength(sAttributeValue,maCurrState,'v'); + break; + default: + // skip + break; + } + } + + // actually export text + Font aFont(maCurrState.maFontFamily, + Size(0, + basegfx::fround(pt100thmm(maCurrState.mnFontSize)))); + aFont.SetAlign(ALIGN_BASELINE); + aFont.SetColor(getVclColor(maCurrState.maFillColor)); + aFont.SetFillColor(getVclColor(maCurrState.maFillColor)); + + if( !maCurrState.maFontStyle.equalsAscii("normal") ) + aFont.SetItalic(ITALIC_NORMAL); // TODO: discriminate + if( !maCurrState.mnFontWeight != 400.0 ) + aFont.SetWeight(WEIGHT_BOLD); // TODO: discriminate + + // extract basic transformations out of CTM + basegfx::B2DTuple aScale, aTranslate; + double fRotate, fShearX; + ::rtl::OUString sTransformValue; + if (maCurrState.maCTM.decompose(aScale, aTranslate, fRotate, fShearX)) + { + rtl::OUString sTransform; + x += aTranslate.getX(); + y += aTranslate.getY(); + + aFont.SetSize( + Size(basegfx::fround(aFont.GetWidth()*aScale.getX()), + basegfx::fround(aFont.GetHeight()*aScale.getY()))); + + if( fRotate ) + aFont.SetOrientation(basegfx::fround(fRotate*1800.0/M_PI)); + } + + // TODO(F2): update bounds + mrOutDev.SetFont(aFont); + const ::rtl::OUString aText( sText.makeStringAndClear() ); + switch( maCurrState.meTextAnchor ) + { + default: + case BEFORE: + break; + + case CENTER: + { + const long nWidth=mrOutDev.GetTextWidth(aText); + x -= nWidth/2; + } + break; + + case AFTER: + x -= mrOutDev.GetTextWidth(aText); + break; + } + mrOutDev.DrawText(Point(basegfx::fround(pt100thmm(x)), + basegfx::fround(pt100thmm(y))), + aText); + break; + } + } + } + + void push() + { + } + + void pop() + { + } + + bool hasGradientOpacity( const Gradient& rGradient ) + { + return + !rGradient.maStops.empty() && + (mrGradientStopVector[ + rGradient.maStops[0]].maStopColor.a != 1.0 || + mrGradientStopVector[ + rGradient.maStops[1]].maStopColor.a != 1.0); + } + + sal_Int8 toByteColor( double val ) + { + // TODO(Q3): duplicated from vcl::unotools + return sal::static_int_cast<sal_Int8>( + basegfx::fround(val*255.0)); + } + + ::Color getVclColor( const ARGBColor& rColor ) + { + const sal_uInt8 nRed ( toByteColor(rColor.r) ); + const sal_uInt8 nGreen( toByteColor(rColor.g) ); + const sal_uInt8 nBlue ( toByteColor(rColor.b) ); + + return ::Color(nRed,nGreen,nBlue); + } + + void renderPathShape(const basegfx::B2DPolyPolygon& rPoly) + { + // we might need to split up polypolygon into multiple path + // shapes (e.g. when emulating line stroking) + State aState = maCurrState; + + // bring polygon from pt coordinate system to 100th millimeter + aState.maCTM.scale(2540.0/72.0,2540.0/72.0); + + basegfx::B2DPolyPolygon aPoly(rPoly); + aPoly.transform(aState.maCTM); + + const basegfx::B2DRange aBounds=basegfx::tools::getRange(aPoly); + maBounds.Union( + Rectangle( + basegfx::fround(aBounds.getMinX()), + basegfx::fround(aBounds.getMinY()), + basegfx::fround(aBounds.getMaxX()), + basegfx::fround(aBounds.getMaxY()))); + + // fill first + mrOutDev.SetLineColor(); + + // do we have a gradient fill? + if( aState.meFillType == GRADIENT && aState.maFillGradient.maStops.size() > 1 ) + { + ::Gradient aGradient; + + if( aState.maFillGradient.meType == Gradient::LINEAR ) + { + // should the optimizeGradientStops method decide that + // this is a three-color gradient, it prolly wanted us + // to take axial instead + aGradient = ::Gradient( aState.maFillGradient.maStops.size() == 3 ? + GRADIENT_AXIAL : + GRADIENT_LINEAR ); + } + else + { + aGradient = ::Gradient( GRADIENT_ELLIPTICAL ); + } + + basegfx::B2DTuple rScale, rTranslate; + double rRotate, rShearX; + if( aState.maFillGradient.maTransform.decompose(rScale, rTranslate, rRotate, rShearX) ) + aGradient.SetAngle( basegfx::fround(rRotate*1800.0/M_PI) ); + aGradient.SetStartColor( getVclColor( + mrGradientStopVector[ + aState.maFillGradient.maStops[0]].maStopColor) ); + aGradient.SetEndColor( getVclColor( + mrGradientStopVector[ + aState.maFillGradient.maStops[1]].maStopColor) ); + + if( hasGradientOpacity(aState.maFillGradient) ) + { + ::Gradient aTransparencyGradient=aGradient; + + const BYTE cTransStart( 255- + basegfx::fround(mrGradientStopVector[ + aState.maFillGradient.maStops[1]].maStopColor.a* + aState.mnFillOpacity*maCurrState.mnOpacity*255.0)); + const Color aTransStart( cTransStart, cTransStart, cTransStart ); + + const BYTE cTransEnd( 255- + basegfx::fround(mrGradientStopVector[ + aState.maFillGradient.maStops[0]].maStopColor.a* + aState.mnFillOpacity*maCurrState.mnOpacity*255.0)); + const Color aTransEnd( cTransEnd, cTransEnd, cTransEnd ); + + // modulate gradient opacity with overall fill opacity + aTransparencyGradient.SetStartColor(aTransStart); + aTransparencyGradient.SetEndColor(aTransEnd); + + VirtualDevice aVDev; + GDIMetaFile aMtf; + + aVDev.EnableOutput( FALSE ); + aVDev.SetMapMode( mrOutDev.GetMapMode() ); + aMtf.Record( &aVDev ); + + aVDev.SetLineColor(); + aVDev.SetFillColor(); + aVDev.DrawGradient(::PolyPolygon(aPoly),aGradient); + + const Rectangle aMtfBounds( + basegfx::fround(aBounds.getMinX()), + basegfx::fround(aBounds.getMinY()), + basegfx::fround(aBounds.getMaxX()), + basegfx::fround(aBounds.getMaxY())); + + MapMode aMap(mrOutDev.GetMapMode()); + aMtf.Stop(); + aMtf.WindStart(); + aMap.SetOrigin( aMtfBounds.TopLeft() ); + aMtf.SetPrefMapMode( aMap ); + aMtf.SetPrefSize( aMtfBounds.GetSize() ); + + mrOutDev.DrawTransparent(aMtf, + aMtfBounds.TopLeft(), + aMtfBounds.GetSize(), + aTransparencyGradient); + } + else + { + mrOutDev.DrawGradient(::PolyPolygon(aPoly),aGradient); + } + } + else + { + if( aState.meFillType == NONE ) + mrOutDev.SetFillColor(); + else + mrOutDev.SetFillColor(getVclColor(aState.maFillColor)); + + if( aState.mnFillOpacity*maCurrState.mnOpacity != 1.0 ) + mrOutDev.DrawTransparent(::PolyPolygon(aPoly), + basegfx::fround( + (1.0-(aState.mnFillOpacity*maCurrState.mnOpacity))*100.0)); + else + mrOutDev.DrawPolyPolygon(::PolyPolygon(aPoly)); + } + + // Stroking now + mrOutDev.SetFillColor(); + + if( aState.meStrokeType != NONE && + (aState.maDashArray.size() || + aState.mnStrokeWidth != 1.0) ) + { + // vcl thick lines are severly borked - generate filled + // polygon instead + std::vector<basegfx::B2DPolyPolygon> aPolys; + aPoly = rPoly; + if( !aState.maDashArray.empty() ) + { + aPoly.clear(); + basegfx::B2DPolyPolygon aSegment; + for( sal_uInt32 i=0; i<rPoly.count(); ++i ) + { + basegfx::tools::applyLineDashing(rPoly, + aState.maDashArray, + &aSegment); + aPoly.append(aSegment); + } + } + + // applied line dashing to original rPoly above, to get + // correctly transformed lengths - need to transform + // again, now + aPoly.transform(aState.maCTM); + + for( sal_uInt32 i=0; i<aPoly.count(); ++i ) + { + // ugly. convert to integer-based tools polygon + // first, and only _then_ remove intersections (we + // might get new ones from the rounding) + aPolys.push_back( + basegfx::tools::stripNeutralPolygons( + basegfx::tools::prepareForPolygonOperation( + ::PolyPolygon( + basegfx::tools::createAreaGeometry( + aPoly.getB2DPolygon(i), + pt100thmm(aState.mnStrokeWidth/2.0), + aState.meLineJoin)).getB2DPolyPolygon()))); + // TODO(F2): line ends + } + + mrOutDev.SetLineColor(); + mrOutDev.SetFillColor(getVclColor(aState.maStrokeColor)); + + for( sal_uInt32 i=0; i<aPolys.size(); ++i ) + { + if( aState.mnStrokeOpacity*maCurrState.mnOpacity != 1.0 ) + mrOutDev.DrawTransparent(::PolyPolygon(aPolys[i]), + basegfx::fround( + (1.0-(aState.mnStrokeOpacity*maCurrState.mnOpacity))*100.0)); + else + mrOutDev.DrawPolyPolygon(::PolyPolygon(aPolys[i])); + + const basegfx::B2DRange aStrokeBounds=basegfx::tools::getRange(aPolys[i]); + maBounds.Union( + Rectangle( + basegfx::fround(aStrokeBounds.getMinX()), + basegfx::fround(aStrokeBounds.getMinY()), + basegfx::fround(aStrokeBounds.getMaxX()), + basegfx::fround(aStrokeBounds.getMaxY()))); + } + } + else + { + if( aState.meStrokeType == NONE ) + mrOutDev.SetLineColor(); + else + mrOutDev.SetLineColor(getVclColor(aState.maStrokeColor)); + + if( aState.mnStrokeOpacity*maCurrState.mnOpacity != 1.0 ) + mrOutDev.DrawTransparent(::PolyPolygon(aPoly), + basegfx::fround( + (1.0-(aState.mnStrokeOpacity*maCurrState.mnOpacity))*100.0)); + else + mrOutDev.DrawPolyPolygon(::PolyPolygon(aPoly)); + } + } + + State maCurrState; + StateMap& mrStateMap; + OutputDevice& mrOutDev; + const std::vector< Gradient >& mrGradientVector; + const std::vector< GradientStop >& mrGradientStopVector; + Rectangle maBounds; +}; + +} // namespace svgi + +bool importSvg(SvStream & rStream, Graphic & rGraphic ) +{ + const uno::Reference<lang::XMultiServiceFactory> xServiceFactory( + ::comphelper::getProcessServiceFactory()); + + uno::Reference<xml::dom::XDocumentBuilder> xDomBuilder( + xServiceFactory->createInstance( + rtl::OUString::createFromAscii("com.sun.star.xml.dom.DocumentBuilder")), + uno::UNO_QUERY_THROW ); + + uno::Reference<io::XInputStream> xStream( + new utl::OInputStreamWrapper(rStream) ); + + uno::Reference<xml::dom::XDocument> xDom( + xDomBuilder->parse(xStream), + uno::UNO_QUERY_THROW ); + + uno::Reference<xml::dom::XElement> xDocElem( xDom->getDocumentElement(), + uno::UNO_QUERY_THROW ); + + VirtualDevice aVDev; + GDIMetaFile aMtf; + + aVDev.EnableOutput( FALSE ); + aMtf.Record( &aVDev ); + aVDev.SetTextAlign(ALIGN_BASELINE); + + // parse styles and fill state stack + svgi::State aInitialState; + svgi::StatePool aStatePool; + svgi::StateMap aStateMap; + svgi::AnnotatingVisitor aVisitor(aStatePool, + aStateMap, + aInitialState, + uno::Reference<xml::sax::XDocumentHandler>()); + svgi::visitElements(aVisitor, xDocElem); + +#ifdef VERBOSE + dumpTree(xDocElem); +#endif + + // render all shapes to mtf + svgi::ShapeRenderingVisitor aRenderer(aStatePool,aStateMap,aVDev, + aVisitor.maGradientVector, + aVisitor.maGradientStopVector); + svgi::visitElements(aRenderer, xDocElem); + + aMtf.Stop(); + + aMtf.WindStart(); + aMtf.SetPrefMapMode( MAP_100TH_MM ); + + // get the document dimensions + + // if the "width" and "height" attributes are missing, inkscape fakes + // A4 portrait for. Let's do the same. + if (!xDocElem->hasAttribute(USTR("width"))) + xDocElem->setAttribute(USTR("width"), USTR("210mm")); + if (!xDocElem->hasAttribute(USTR("height"))) + xDocElem->setAttribute(USTR("height"), USTR("297mm")); + + aMtf.SetPrefSize( + Size( + std::max( + sal_Int32(aRenderer.maBounds.Right()), + basegfx::fround( svgi::pt100thmm(svgi::convLength(xDocElem->getAttribute(USTR("width")),aInitialState,'h')) )), + std::max( + sal_Int32(aRenderer.maBounds.Bottom()), + basegfx::fround( svgi::pt100thmm(svgi::convLength(xDocElem->getAttribute(USTR("height")),aInitialState,'v')) )))); + + rGraphic = aMtf; + + return sal_True; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |