summaryrefslogtreecommitdiffstats
path: root/filter/source/svg/svgwriter.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'filter/source/svg/svgwriter.cxx')
-rw-r--r--filter/source/svg/svgwriter.cxx4096
1 files changed, 4096 insertions, 0 deletions
diff --git a/filter/source/svg/svgwriter.cxx b/filter/source/svg/svgwriter.cxx
new file mode 100644
index 000000000..3a3fc8a5c
--- /dev/null
+++ b/filter/source/svg/svgwriter.cxx
@@ -0,0 +1,4096 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "svgfilter.hxx"
+#include "svgfontexport.hxx"
+#include "svgwriter.hxx"
+
+#include <comphelper/base64.hxx>
+#include <cppuhelper/supportsservice.hxx>
+#include <sal/log.hxx>
+#include <vcl/unohelp.hxx>
+#include <vcl/cvtgrf.hxx>
+#include <vcl/metric.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/filter/SvmReader.hxx>
+#include <vcl/filter/SvmWriter.hxx>
+#include <tools/fract.hxx>
+#include <tools/helpers.hxx>
+#include <tools/stream.hxx>
+#include <xmloff/namespacemap.hxx>
+#include <xmloff/unointerfacetouniqueidentifiermapper.hxx>
+#include <i18nlangtag/languagetag.hxx>
+#include <o3tl/string_view.hxx>
+
+#include <com/sun/star/container/XEnumerationAccess.hpp>
+#include <com/sun/star/container/XIndexReplace.hpp>
+#include <com/sun/star/graphic/XGraphic.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/style/NumberingType.hpp>
+#include <com/sun/star/text/XTextField.hpp>
+
+#include <memory>
+
+
+constexpr OUStringLiteral aPrefixClipPathId = u"clip_path_";
+
+constexpr OUStringLiteral aXMLElemG = u"g";
+constexpr OUStringLiteral aXMLElemDefs = u"defs";
+constexpr OUStringLiteral aXMLElemText = u"text";
+constexpr OUStringLiteral aXMLElemTspan = u"tspan";
+constexpr OUStringLiteral aXMLElemLinearGradient = u"linearGradient";
+constexpr OUStringLiteral aXMLElemStop = u"stop";
+
+constexpr OUStringLiteral aXMLAttrTransform = u"transform";
+constexpr OUStringLiteral aXMLAttrStyle = u"style";
+constexpr OUStringLiteral aXMLAttrId = u"id";
+constexpr OUStringLiteral aXMLAttrX = u"x";
+constexpr OUStringLiteral aXMLAttrY = u"y";
+constexpr OUStringLiteral aXMLAttrX1 = u"x1";
+constexpr OUStringLiteral aXMLAttrY1 = u"y1";
+constexpr OUStringLiteral aXMLAttrX2 = u"x2";
+constexpr OUStringLiteral aXMLAttrY2 = u"y2";
+constexpr OUStringLiteral aXMLAttrCX = u"cx";
+constexpr OUStringLiteral aXMLAttrCY = u"cy";
+constexpr OUStringLiteral aXMLAttrRX = u"rx";
+constexpr OUStringLiteral aXMLAttrRY = u"ry";
+constexpr OUStringLiteral aXMLAttrWidth = u"width";
+constexpr OUStringLiteral aXMLAttrHeight = u"height";
+constexpr OUStringLiteral aXMLAttrStrokeWidth = u"stroke-width";
+constexpr OUStringLiteral aXMLAttrFill = u"fill";
+constexpr OUStringLiteral aXMLAttrFontFamily = u"font-family";
+constexpr OUStringLiteral aXMLAttrFontSize = u"font-size";
+constexpr OUStringLiteral aXMLAttrFontStyle = u"font-style";
+constexpr OUStringLiteral aXMLAttrFontWeight = u"font-weight";
+constexpr OUStringLiteral aXMLAttrTextDecoration = u"text-decoration";
+constexpr OUStringLiteral aXMLAttrXLinkHRef = u"xlink:href";
+constexpr OUStringLiteral aXMLAttrGradientUnits = u"gradientUnits";
+constexpr OUStringLiteral aXMLAttrOffset = u"offset";
+constexpr OUStringLiteral aXMLAttrStopColor = u"stop-color";
+constexpr OUStringLiteral aXMLAttrStrokeLinejoin = u"stroke-linejoin";
+constexpr OUStringLiteral aXMLAttrStrokeLinecap = u"stroke-linecap";
+
+
+vcl::PushFlags SVGContextHandler::getPushFlags() const
+{
+ if (maStateStack.empty())
+ return vcl::PushFlags::NONE;
+
+ const PartialState& rPartialState = maStateStack.top();
+ return rPartialState.meFlags;
+}
+
+SVGState& SVGContextHandler::getCurrentState()
+{
+ return maCurrentState;
+}
+
+void SVGContextHandler::pushState( vcl::PushFlags eFlags )
+{
+ PartialState aPartialState;
+ aPartialState.meFlags = eFlags;
+
+ if (eFlags & vcl::PushFlags::FONT)
+ {
+ aPartialState.setFont( maCurrentState.aFont );
+ }
+
+ if (eFlags & vcl::PushFlags::CLIPREGION)
+ {
+ aPartialState.mnRegionClipPathId = maCurrentState.nRegionClipPathId;
+ }
+
+ maStateStack.push( std::move(aPartialState) );
+}
+
+void SVGContextHandler::popState()
+{
+ if (maStateStack.empty())
+ return;
+
+ const PartialState& rPartialState = maStateStack.top();
+ vcl::PushFlags eFlags = rPartialState.meFlags;
+
+ if (eFlags & vcl::PushFlags::FONT)
+ {
+ maCurrentState.aFont = rPartialState.getFont( vcl::Font() );
+ }
+
+ if (eFlags & vcl::PushFlags::CLIPREGION)
+ {
+ maCurrentState.nRegionClipPathId = rPartialState.mnRegionClipPathId;
+ }
+
+ maStateStack.pop();
+}
+
+SVGAttributeWriter::SVGAttributeWriter( SVGExport& rExport, SVGFontExport& rFontExport, SVGState& rCurState )
+ : mrExport( rExport )
+ , mrFontExport( rFontExport )
+ , mrCurrentState( rCurState )
+{
+}
+
+
+SVGAttributeWriter::~SVGAttributeWriter()
+{
+}
+
+
+double SVGAttributeWriter::ImplRound( double fValue )
+{
+ return floor( fValue * pow( 10.0, 3 ) + 0.5 ) / pow( 10.0, 3 );
+}
+
+
+void SVGAttributeWriter::ImplGetColorStr( const Color& rColor, OUString& rColorStr )
+{
+ if( rColor.GetAlpha() == 0 )
+ rColorStr = "none";
+ else
+ {
+ rColorStr = "rgb(" + OUString::number(rColor.GetRed()) + "," + OUString::number(rColor.GetGreen()) +
+ "," + OUString::number(rColor.GetBlue()) + ")";
+ }
+}
+
+
+void SVGAttributeWriter::AddColorAttr( const OUString& pColorAttrName,
+ const OUString& pColorOpacityAttrName,
+ const Color& rColor )
+{
+ OUString aColor, aColorOpacity;
+
+ ImplGetColorStr( rColor, aColor );
+
+ if( rColor.GetAlpha() < 255 && rColor.GetAlpha() > 0 )
+ aColorOpacity = OUString::number( ImplRound( rColor.GetAlpha() / 255.0 ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, pColorAttrName, aColor );
+
+ if( !aColorOpacity.isEmpty() && mrExport.IsUseOpacity() )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, pColorOpacityAttrName, aColorOpacity );
+}
+
+
+void SVGAttributeWriter::AddPaintAttr( const Color& rLineColor, const Color& rFillColor,
+ const tools::Rectangle* pObjBoundRect, const Gradient* pFillGradient )
+{
+ // Fill
+ if( pObjBoundRect && pFillGradient )
+ {
+ OUString aGradientId;
+
+ AddGradientDef( *pObjBoundRect, *pFillGradient, aGradientId );
+
+ if( !aGradientId.isEmpty() )
+ {
+ OUString aGradientURL = "url(#" + aGradientId + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFill, aGradientURL );
+ }
+ }
+ else
+ AddColorAttr( aXMLAttrFill, "fill-opacity", rFillColor );
+
+ // Stroke
+ AddColorAttr( "stroke", "stroke-opacity", rLineColor );
+}
+
+
+void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const Gradient& rGradient, OUString& rGradientId )
+{
+ if( rObjRect.GetWidth() && rObjRect.GetHeight() &&
+ ( rGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial ||
+ rGradient.GetStyle() == GradientStyle::Radial || rGradient.GetStyle() == GradientStyle::Elliptical ) )
+ {
+ SvXMLElementExport aDesc( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+ Color aStartColor( rGradient.GetStartColor() ), aEndColor( rGradient.GetEndColor() );
+ Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+ Point aObjRectCenter( rObjRect.Center() );
+ tools::Polygon aPoly( rObjRect );
+ static sal_Int32 nCurGradientId = 1;
+
+ aPoly.Rotate( aObjRectCenter, nAngle );
+ tools::Rectangle aRect( aPoly.GetBoundRect() );
+
+ // adjust start/end colors with intensities
+ aStartColor.SetRed( static_cast<sal_uInt8>( ( aStartColor.GetRed() * rGradient.GetStartIntensity() ) / 100 ) );
+ aStartColor.SetGreen( static_cast<sal_uInt8>( ( aStartColor.GetGreen() * rGradient.GetStartIntensity() ) / 100 ) );
+ aStartColor.SetBlue( static_cast<sal_uInt8>( ( aStartColor.GetBlue() * rGradient.GetStartIntensity() ) / 100 ) );
+
+ aEndColor.SetRed( static_cast<sal_uInt8>( ( aEndColor.GetRed() * rGradient.GetEndIntensity() ) / 100 ) );
+ aEndColor.SetGreen( static_cast<sal_uInt8>( ( aEndColor.GetGreen() * rGradient.GetEndIntensity() ) / 100 ) );
+ aEndColor.SetBlue( static_cast<sal_uInt8>( ( aEndColor.GetBlue() * rGradient.GetEndIntensity() ) / 100 ) );
+
+ rGradientId = "Gradient_" + OUString::number( nCurGradientId++ );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, rGradientId );
+
+ {
+ std::unique_ptr< SvXMLElementExport > apGradient;
+ OUString aColorStr;
+
+ if( rGradient.GetStyle() == GradientStyle::Linear || rGradient.GetStyle() == GradientStyle::Axial )
+ {
+ tools::Polygon aLinePoly( 2 );
+
+ aLinePoly[ 0 ] = Point( aObjRectCenter.X(), aRect.Top() );
+ aLinePoly[ 1 ] = Point( aObjRectCenter.X(), aRect.Bottom() );
+
+ aLinePoly.Rotate( aObjRectCenter, nAngle );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits, "userSpaceOnUse" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aLinePoly[ 0 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aLinePoly[ 0 ].Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aLinePoly[ 1 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aLinePoly[ 1 ].Y() ) );
+
+ apGradient.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true ) );
+
+ // write stop values
+ double fBorder = static_cast< double >( rGradient.GetBorder() ) *
+ ( ( rGradient.GetStyle() == GradientStyle::Axial ) ? 0.005 : 0.01 );
+
+ ImplGetColorStr( ( rGradient.GetStyle() == GradientStyle::Axial ) ? aEndColor : aStartColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( fBorder ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc2( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+
+ if( rGradient.GetStyle() == GradientStyle::Axial )
+ {
+ ImplGetColorStr( aStartColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( 0.5 ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc3( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+ }
+
+ if( rGradient.GetStyle() != GradientStyle::Axial )
+ fBorder = 0.0;
+
+ ImplGetColorStr( aEndColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( ImplRound( 1.0 - fBorder ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc4( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+ }
+ else
+ {
+ const double fCenterX = rObjRect.Left() + rObjRect.GetWidth() * rGradient.GetOfsX() * 0.01;
+ const double fCenterY = rObjRect.Top() + rObjRect.GetHeight() * rGradient.GetOfsY() * 0.01;
+ const double fRadius = std::hypot(rObjRect.GetWidth(), rObjRect.GetHeight()) * 0.5;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits, "userSpaceOnUse" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCX, OUString::number( ImplRound( fCenterX ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCY, OUString::number( ImplRound( fCenterY ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "r", OUString::number( ImplRound( fRadius ) ) );
+
+ apGradient.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, "radialGradient", true, true ) );
+
+ // write stop values
+ ImplGetColorStr( aEndColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( 0.0 ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc5( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+
+ ImplGetColorStr( aStartColor, aColorStr );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset,
+ OUString::number( ImplRound( 1.0 - rGradient.GetBorder() * 0.01 ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStopColor, aColorStr );
+
+ {
+ SvXMLElementExport aDesc6( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+ }
+ }
+ }
+ else
+ rGradientId.clear();
+}
+
+
+void SVGAttributeWriter::SetFontAttr( const vcl::Font& rFont )
+{
+ vcl::Font& rCurFont = mrCurrentState.aFont;
+
+ if( rFont == rCurFont )
+ return;
+
+ OUString aFontStyle;
+ sal_Int32 nFontWeight;
+
+ rCurFont = rFont;
+
+ // Font Family
+ setFontFamily();
+
+ // Font Size
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontSize,
+ OUString::number( rFont.GetFontHeight() ) + "px" );
+
+ // Font Style
+ if( rFont.GetItalic() != ITALIC_NONE )
+ {
+ if( rFont.GetItalic() == ITALIC_OBLIQUE )
+ aFontStyle = "oblique";
+ else
+ aFontStyle = "italic";
+ }
+ else
+ aFontStyle = "normal";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontStyle, aFontStyle );
+
+ // Font Weight
+ switch( rFont.GetWeight() )
+ {
+ case WEIGHT_THIN: nFontWeight = 100; break;
+ case WEIGHT_ULTRALIGHT: nFontWeight = 200; break;
+ case WEIGHT_LIGHT: nFontWeight = 300; break;
+ case WEIGHT_SEMILIGHT: nFontWeight = 400; break;
+ case WEIGHT_NORMAL: nFontWeight = 400; break;
+ case WEIGHT_MEDIUM: nFontWeight = 500; break;
+ case WEIGHT_SEMIBOLD: nFontWeight = 600; break;
+ case WEIGHT_BOLD: nFontWeight = 700; break;
+ case WEIGHT_ULTRABOLD: nFontWeight = 800; break;
+ case WEIGHT_BLACK: nFontWeight = 900; break;
+ default: nFontWeight = 400; break;
+ }
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontWeight, OUString::number( nFontWeight ) );
+
+ if( mrExport.IsUseNativeTextDecoration() )
+ {
+ OUString aTextDecoration;
+ if( rFont.GetUnderline() != LINESTYLE_NONE || rFont.GetStrikeout() != STRIKEOUT_NONE )
+ {
+ if( rFont.GetUnderline() != LINESTYLE_NONE )
+ aTextDecoration = "underline ";
+
+ if( rFont.GetStrikeout() != STRIKEOUT_NONE )
+ aTextDecoration += "line-through ";
+ }
+ else
+ aTextDecoration = "none";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, aTextDecoration );
+ }
+
+ startFontSettings();
+}
+
+
+void SVGAttributeWriter::startFontSettings()
+{
+ endFontSettings();
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ mpElemFont.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ) );
+ }
+ else
+ {
+ mpElemFont.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, true, true ) );
+ }
+}
+
+
+void SVGAttributeWriter::endFontSettings()
+{
+ mpElemFont.reset();
+}
+
+
+void SVGAttributeWriter::setFontFamily()
+{
+ vcl::Font& rCurFont = mrCurrentState.aFont;
+
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, mrFontExport.GetMappedFontName( rCurFont.GetFamilyName() ) );
+ }
+ else
+ {
+ const OUString& rsFontName = rCurFont.GetFamilyName();
+ OUString sFontFamily( rsFontName.getToken( 0, ';' ) );
+ FontPitch ePitch = rCurFont.GetPitch();
+ if( ePitch == PITCH_FIXED )
+ {
+ sFontFamily += ", monospace";
+ }
+ else
+ {
+ FontFamily eFamily = rCurFont.GetFamilyType();
+ if( eFamily == FAMILY_ROMAN )
+ sFontFamily += ", serif";
+ else if( eFamily == FAMILY_SWISS )
+ sFontFamily += ", sans-serif";
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, sFontFamily );
+ }
+}
+
+SVGTextWriter::SVGTextWriter(SVGExport& rExport, SVGAttributeWriter& rAttributeWriter,
+ SVGActionWriter& rActionWriter)
+: mrExport( rExport ),
+ mrAttributeWriter( rAttributeWriter ),
+ mrActionWriter(rActionWriter),
+ mpVDev( nullptr ),
+ mbIsTextShapeStarted( false ),
+ mpTextEmbeddedBitmapMtf( nullptr ),
+ mpTargetMapMode( nullptr ),
+ mnLeftTextPortionLength( 0 ),
+ maTextPos(0,0),
+ mnTextWidth(0),
+ mbPositioningNeeded( false ),
+ mbIsNewListItem( false ),
+ meNumberingType(0),
+ mcBulletChar(0),
+ mbIsListLevelStyleImage( false ),
+ mbLineBreak( false ),
+ mbIsURLField( false ),
+ mbIsPlaceholderShape( false )
+{
+}
+
+
+SVGTextWriter::~SVGTextWriter()
+{
+ endTextParagraph();
+}
+
+
+void SVGTextWriter::implRegisterInterface( const Reference< XInterface >& rxIf )
+{
+ if( rxIf.is() )
+ mrExport.getInterfaceToIdentifierMapper().registerReference( rxIf );
+}
+
+
+const OUString & SVGTextWriter::implGetValidIDFromInterface( const Reference< XInterface >& rxIf )
+{
+ return mrExport.getInterfaceToIdentifierMapper().getIdentifier( rxIf );
+}
+
+
+void SVGTextWriter::implMap( const Size& rSz, Size& rDstSz ) const
+{
+ if( mpVDev && mpTargetMapMode )
+ rDstSz = OutputDevice::LogicToLogic( rSz, mpVDev->GetMapMode(), *mpTargetMapMode );
+ else
+ OSL_FAIL( "SVGTextWriter::implMap: invalid virtual device or map mode." );
+}
+
+
+void SVGTextWriter::implMap( const Point& rPt, Point& rDstPt ) const
+{
+ if( mpVDev && mpTargetMapMode )
+ rDstPt = OutputDevice::LogicToLogic( rPt, mpVDev->GetMapMode(), *mpTargetMapMode );
+ else
+ OSL_FAIL( "SVGTextWriter::implMap: invalid virtual device or map mode." );
+}
+
+
+void SVGTextWriter::implSetCurrentFont()
+{
+ if( mpVDev )
+ {
+ maCurrentFont = mpVDev->GetFont();
+ Size aSz;
+
+ implMap( Size( 0, maCurrentFont.GetFontHeight() ), aSz );
+
+ maCurrentFont.SetFontHeight( aSz.Height() );
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::implSetCorrectFontHeight: invalid virtual device." );
+ }
+}
+
+
+template< typename SubType >
+bool SVGTextWriter::implGetTextPosition( const MetaAction* pAction, Point& raPos, bool& rbEmpty )
+{
+ const SubType* pA = static_cast<const SubType*>(pAction);
+ sal_uInt16 nLength = pA->GetLen();
+ rbEmpty = ( nLength == 0 );
+ if( !rbEmpty )
+ {
+ raPos = pA->GetPoint();
+ return true;
+ }
+ return false;
+}
+
+
+template<>
+bool SVGTextWriter::implGetTextPosition<MetaTextRectAction>( const MetaAction* pAction, Point& raPos, bool& rbEmpty )
+{
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ sal_uInt16 nLength = pA->GetText().getLength();
+ rbEmpty = ( nLength == 0 );
+ if( !rbEmpty )
+ {
+ raPos = pA->GetRect().TopLeft();
+ return true;
+ }
+ return false;
+}
+
+
+template< typename SubType >
+bool SVGTextWriter::implGetTextPositionFromBitmap( const MetaAction* pAction, Point& raPos, bool& rbEmpty )
+{
+ const SubType* pA = static_cast<const SubType*>(pAction);
+ raPos = pA->GetPoint();
+ rbEmpty = false;
+ return true;
+}
+
+
+/** setTextPosition
+ * Set the start position of the next line of text. In case no text is found
+ * the current action index is updated to the index value we reached while
+ * searching for text.
+ *
+ * @returns {sal_Int32}
+ * -2 if no text found and end of line is reached
+ * -1 if no text found and end of paragraph is reached
+ * 0 if no text found and end of text shape is reached
+ * 1 if text found!
+ */
+sal_Int32 SVGTextWriter::setTextPosition(const GDIMetaFile& rMtf, size_t& nCurAction,
+ sal_uInt32 nWriteFlags)
+{
+ Point aPos;
+ size_t nCount = rMtf.GetActionSize();
+ bool bEOL = false;
+ bool bEOP = false;
+ bool bETS = false;
+ bool bConfigured = false;
+ bool bEmpty = true;
+
+ size_t nActionIndex = nCurAction + 1;
+ for( ; nActionIndex < nCount; ++nActionIndex )
+ {
+ const MetaAction* pAction = rMtf.GetAction( nActionIndex );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::TEXT:
+ {
+ bConfigured = implGetTextPosition<MetaTextAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ bConfigured = implGetTextPosition<MetaTextRectAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ bConfigured = implGetTextPosition<MetaTextArrayAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ const MetaFloatTransparentAction* pA
+ = static_cast<const MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTmpMtf(pA->GetGDIMetaFile());
+ size_t nTmpAction = 0;
+ if (setTextPosition(aTmpMtf, nTmpAction, nWriteFlags) == 1)
+ {
+ // Text is found in the inner metafile.
+ bConfigured = true;
+
+ // nTextFound == 1 is only possible if the inner setTextPosition() had bEmpty ==
+ // false, adjust our bEmpty accordingly.
+ bEmpty = false;
+
+ mrActionWriter.StartMask(pA->GetPoint(), pA->GetSize(), pA->GetGradient(),
+ nWriteFlags, &maTextOpacity);
+ }
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ bConfigured = implGetTextPosition<MetaStretchTextAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ bConfigured = implGetTextPositionFromBitmap<MetaBmpScaleAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ bConfigured = implGetTextPositionFromBitmap<MetaBmpExScaleAction>( pAction, aPos, bEmpty );
+ }
+ break;
+
+ // If we reach the end of the current line, paragraph or text shape
+ // without finding any text we stop searching
+ case MetaActionType::COMMENT:
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ const OString& rsComment = pA->GetComment();
+ if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOL" ) )
+ {
+ bEOL = true;
+ }
+ else if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOP" ) )
+ {
+ bEOP = true;
+
+ OUString sContent;
+ while( nextTextPortion() )
+ {
+ sContent = mrCurrentTextPortion->getString();
+ if( sContent.isEmpty() )
+ {
+ continue;
+ }
+ else
+ {
+ if( sContent == "\n" )
+ mbLineBreak = true;
+ }
+ }
+ if( nextParagraph() )
+ {
+ while( nextTextPortion() )
+ {
+ sContent = mrCurrentTextPortion->getString();
+ if( sContent.isEmpty() )
+ {
+ continue;
+ }
+ else
+ {
+ if( sContent == "\n" )
+ mbLineBreak = true;
+ }
+ }
+ }
+ }
+ else if( rsComment.equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) )
+ {
+ bETS = true;
+ }
+ }
+ break;
+ default: break;
+ }
+ if( bConfigured || bEOL || bEOP || bETS ) break;
+ }
+ implMap( aPos, maTextPos );
+
+ if( bEmpty )
+ {
+ nCurAction = nActionIndex;
+ return ( bEOL ? -2 : ( bEOP ? -1 : 0 ) );
+ }
+ else
+ {
+ return 1;
+ }
+}
+
+
+void SVGTextWriter::setTextProperties( const GDIMetaFile& rMtf, size_t nCurAction )
+{
+ size_t nCount = rMtf.GetActionSize();
+ bool bEOP = false;
+ bool bConfigured = false;
+ for( size_t nActionIndex = nCurAction + 1; nActionIndex < nCount; ++nActionIndex )
+ {
+ const MetaAction* pAction = rMtf.GetAction( nActionIndex );
+ const MetaActionType nType = pAction->GetType();
+ switch( nType )
+ {
+ case MetaActionType::TEXTLINECOLOR:
+ case MetaActionType::TEXTFILLCOLOR:
+ case MetaActionType::TEXTCOLOR:
+ case MetaActionType::TEXTALIGN:
+ case MetaActionType::FONT:
+ case MetaActionType::LAYOUTMODE:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ if( pA->GetLen() > 2 )
+ bConfigured = true;
+ }
+ break;
+ case MetaActionType::TEXTRECT:
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+ if( pA->GetText().getLength() > 2 )
+ bConfigured = true;
+ }
+ break;
+ case MetaActionType::TEXTARRAY:
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ if( pA->GetLen() > 2 )
+ bConfigured = true;
+ }
+ break;
+ case MetaActionType::STRETCHTEXT:
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ if( pA->GetLen() > 2 )
+ bConfigured = true;
+ }
+ break;
+ // If we reach the end of the paragraph without finding any text
+ // we stop searching
+ case MetaActionType::COMMENT:
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ const OString& rsComment = pA->GetComment();
+ if( rsComment.equalsIgnoreAsciiCase( "XTEXT_EOP" ) )
+ {
+ bEOP = true;
+ }
+ }
+ break;
+ default: break;
+ }
+ if( bConfigured || bEOP ) break;
+ }
+}
+
+
+void SVGTextWriter::addFontAttributes( bool bIsTextContainer )
+{
+ implSetCurrentFont();
+
+ if( maCurrentFont == maParentFont )
+ return;
+
+ const OUString& rsCurFontName = maCurrentFont.GetFamilyName();
+ tools::Long nCurFontSize = maCurrentFont.GetFontHeight();
+ FontItalic eCurFontItalic = maCurrentFont.GetItalic();
+ FontWeight eCurFontWeight = maCurrentFont.GetWeight();
+
+ const OUString& rsParFontName = maParentFont.GetFamilyName();
+ tools::Long nParFontSize = maParentFont.GetFontHeight();
+ FontItalic eParFontItalic = maParentFont.GetItalic();
+ FontWeight eParFontWeight = maParentFont.GetWeight();
+
+
+ // Font Family
+ if( rsCurFontName != rsParFontName )
+ {
+ implSetFontFamily();
+ }
+
+ // Font Size
+ if( nCurFontSize != nParFontSize )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontSize,
+ OUString::number( nCurFontSize ) + "px" );
+ }
+
+ // Font Style
+ if( eCurFontItalic != eParFontItalic )
+ {
+ OUString sFontStyle;
+ if( eCurFontItalic != ITALIC_NONE )
+ {
+ if( eCurFontItalic == ITALIC_OBLIQUE )
+ sFontStyle = "oblique";
+ else
+ sFontStyle = "italic";
+ }
+ else
+ {
+ sFontStyle = "normal";
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontStyle, sFontStyle );
+ }
+
+ // Font Weight
+ if( eCurFontWeight != eParFontWeight )
+ {
+ sal_Int32 nFontWeight;
+ switch( eCurFontWeight )
+ {
+ case WEIGHT_THIN: nFontWeight = 100; break;
+ case WEIGHT_ULTRALIGHT: nFontWeight = 200; break;
+ case WEIGHT_LIGHT: nFontWeight = 300; break;
+ case WEIGHT_SEMILIGHT: nFontWeight = 400; break;
+ case WEIGHT_NORMAL: nFontWeight = 400; break;
+ case WEIGHT_MEDIUM: nFontWeight = 500; break;
+ case WEIGHT_SEMIBOLD: nFontWeight = 600; break;
+ case WEIGHT_BOLD: nFontWeight = 700; break;
+ case WEIGHT_ULTRABOLD: nFontWeight = 800; break;
+ case WEIGHT_BLACK: nFontWeight = 900; break;
+ default: nFontWeight = 400; break;
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontWeight, OUString::number( nFontWeight ) );
+ }
+
+
+ if( mrExport.IsUseNativeTextDecoration() )
+ {
+ FontLineStyle eCurFontLineStyle = maCurrentFont.GetUnderline();
+ FontStrikeout eCurFontStrikeout = maCurrentFont.GetStrikeout();
+
+ FontLineStyle eParFontLineStyle = maParentFont.GetUnderline();
+ FontStrikeout eParFontStrikeout = maParentFont.GetStrikeout();
+
+ OUString sTextDecoration;
+ bool bIsDecorationChanged = false;
+ if( eCurFontLineStyle != eParFontLineStyle )
+ {
+ if( eCurFontLineStyle != LINESTYLE_NONE )
+ sTextDecoration = "underline";
+ bIsDecorationChanged = true;
+ }
+ if( eCurFontStrikeout != eParFontStrikeout )
+ {
+ if( eCurFontStrikeout != STRIKEOUT_NONE )
+ {
+ if( !sTextDecoration.isEmpty() )
+ sTextDecoration += " ";
+ sTextDecoration += "line-through";
+ }
+ bIsDecorationChanged = true;
+ }
+
+ if( !sTextDecoration.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, sTextDecoration );
+ }
+ else if( bIsDecorationChanged )
+ {
+ sTextDecoration = "none";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTextDecoration, sTextDecoration );
+ }
+ }
+
+ if( bIsTextContainer )
+ maParentFont = maCurrentFont;
+}
+
+
+void SVGTextWriter::implSetFontFamily()
+{
+ const OUString& rsFontName = maCurrentFont.GetFamilyName();
+ OUString sFontFamily( rsFontName.getToken( 0, ';' ) );
+ FontPitch ePitch = maCurrentFont.GetPitch();
+ if( ePitch == PITCH_FIXED )
+ {
+ sFontFamily += ", monospace";
+ }
+ else
+ {
+ FontFamily eFamily = maCurrentFont.GetFamilyType();
+ if( eFamily == FAMILY_ROMAN )
+ sFontFamily += ", serif";
+ else if( eFamily == FAMILY_SWISS )
+ sFontFamily += ", sans-serif";
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrFontFamily, sFontFamily );
+}
+
+
+void SVGTextWriter::createParagraphEnumeration()
+{
+ if( mrTextShape.is() )
+ {
+ msShapeId = implGetValidIDFromInterface( Reference<XInterface>(mrTextShape, UNO_QUERY) );
+
+ Reference< XEnumerationAccess > xEnumerationAccess( mrTextShape, UNO_QUERY_THROW );
+ Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
+ if( xEnumeration.is() )
+ {
+ mrParagraphEnumeration.set( xEnumeration );
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::createParagraphEnumeration: no valid xEnumeration interface found." );
+ }
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::createParagraphEnumeration: no valid XText interface found." );
+ }
+}
+
+
+bool SVGTextWriter::nextParagraph()
+{
+ mrTextPortionEnumeration.clear();
+ mrCurrentTextParagraph.clear();
+ mbIsNewListItem = false;
+ mbIsListLevelStyleImage = false;
+
+ if( !mrParagraphEnumeration || !mrParagraphEnumeration->hasMoreElements() )
+ return false;
+
+ Reference < XTextContent > xTextContent( mrParagraphEnumeration->nextElement(), UNO_QUERY_THROW );
+ if( xTextContent.is() )
+ {
+ Reference< XServiceInfo > xServiceInfo( xTextContent, UNO_QUERY_THROW );
+#if OSL_DEBUG_LEVEL > 0
+ OUString sInfo;
+#endif
+ if( xServiceInfo->supportsService( "com.sun.star.text.Paragraph" ) )
+ {
+ mrCurrentTextParagraph.set( xTextContent );
+ Reference< XPropertySet > xPropSet( xTextContent, UNO_QUERY_THROW );
+ Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
+ if( xPropSetInfo->hasPropertyByName( "NumberingLevel" ) )
+ {
+ sal_Int16 nListLevel = 0;
+ if( xPropSet->getPropertyValue( "NumberingLevel" ) >>= nListLevel )
+ {
+ mbIsNewListItem = true;
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = "NumberingLevel: " + OUString::number( nListLevel );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "style", sInfo );
+#endif
+ Reference< XIndexReplace > xNumRules;
+ if( xPropSetInfo->hasPropertyByName( "NumberingRules" ) )
+ {
+ xPropSet->getPropertyValue( "NumberingRules" ) >>= xNumRules;
+ }
+ if( xNumRules.is() && ( nListLevel < xNumRules->getCount() ) )
+ {
+ bool bIsNumbered = true;
+ OUString sNumberingIsNumber("NumberingIsNumber");
+ if( xPropSetInfo->hasPropertyByName( sNumberingIsNumber ) )
+ {
+ if( !(xPropSet->getPropertyValue( sNumberingIsNumber ) >>= bIsNumbered ) )
+ {
+ OSL_FAIL( "numbered paragraph without number info" );
+ bIsNumbered = false;
+ }
+#if OSL_DEBUG_LEVEL > 0
+ if( bIsNumbered )
+ {
+ sInfo = "true";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "is-numbered", sInfo );
+ }
+#endif
+ }
+ mbIsNewListItem = bIsNumbered;
+
+ if( bIsNumbered )
+ {
+ Sequence<PropertyValue> aProps;
+ if( xNumRules->getByIndex( nListLevel ) >>= aProps )
+ {
+ sal_Int16 eType = NumberingType::CHAR_SPECIAL;
+ sal_Unicode cBullet = 0xf095;
+ const sal_Int32 nCount = aProps.getLength();
+ const PropertyValue* pPropArray = aProps.getConstArray();
+ for( sal_Int32 i = 0; i < nCount; ++i )
+ {
+ const PropertyValue& rProp = pPropArray[i];
+ if( rProp.Name == "NumberingType" )
+ {
+ rProp.Value >>= eType;
+ }
+ else if( rProp.Name == "BulletChar" )
+ {
+ OUString sValue;
+ rProp.Value >>= sValue;
+ if( !sValue.isEmpty() )
+ {
+ cBullet = sValue[0];
+ }
+ }
+ }
+ meNumberingType = eType;
+ mbIsListLevelStyleImage = ( NumberingType::BITMAP == meNumberingType );
+ if( NumberingType::CHAR_SPECIAL == meNumberingType )
+ {
+ if( cBullet )
+ {
+ if( cBullet < ' ' )
+ {
+ cBullet = 0xF000 + 149;
+ }
+ mcBulletChar = cBullet;
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = OUString::number( static_cast<sal_Int32>(cBullet) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "bullet-char", sInfo );
+#endif
+ }
+
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+ Reference< XEnumerationAccess > xEnumerationAccess( xTextContent, UNO_QUERY_THROW );
+ Reference< XEnumeration > xEnumeration( xEnumerationAccess->createEnumeration(), UNO_SET_THROW );
+ if( xEnumeration.is() && xEnumeration->hasMoreElements() )
+ {
+ mrTextPortionEnumeration.set( xEnumeration );
+ }
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = "Paragraph";
+#endif
+ }
+ else if( xServiceInfo->supportsService( "com.sun.star.text.Table" ) )
+ {
+ OSL_FAIL( "SVGTextWriter::nextParagraph: text tables are not handled." );
+#if OSL_DEBUG_LEVEL > 0
+ sInfo = "Table";
+#endif
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::nextParagraph: Unknown text content." );
+ return false;
+ }
+#if OSL_DEBUG_LEVEL > 0
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", sInfo );
+ SvXMLElementExport aParaElem( mrExport, XML_NAMESPACE_NONE, "desc", mbIWS, mbIWS );
+#endif
+ }
+ else
+ {
+ OSL_FAIL( "SVGTextWriter::nextParagraph: no XServiceInfo interface available for text content." );
+ return false;
+ }
+
+ const OUString& rParagraphId = implGetValidIDFromInterface( Reference<XInterface>(xTextContent, UNO_QUERY) );
+ if( !rParagraphId.isEmpty() )
+ {
+ // if there is id for empty paragraph we need to create a empty text paragraph
+ Reference < XTextRange > xRange( xTextContent, UNO_QUERY_THROW );
+ if ( xRange.is() && xRange->getString().isEmpty() )
+ {
+ endTextParagraph();
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextParagraph" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rParagraphId );
+ mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ));
+ }
+ else
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rParagraphId );
+ }
+ }
+ return true;
+}
+
+
+bool SVGTextWriter::nextTextPortion()
+{
+ mrCurrentTextPortion.clear();
+ mbIsURLField = false;
+ if( !mrTextPortionEnumeration || !mrTextPortionEnumeration->hasMoreElements() )
+ return false;
+
+ mbIsPlaceholderShape = false;
+ Reference< XPropertySet > xPortionPropSet( mrTextPortionEnumeration->nextElement(), UNO_QUERY );
+ Reference< XPropertySetInfo > xPortionPropInfo( xPortionPropSet->getPropertySetInfo() );
+ Reference < XTextRange > xPortionTextRange( xPortionPropSet, UNO_QUERY);
+ if( !xPortionPropSet || !xPortionPropInfo
+ || !xPortionPropInfo->hasPropertyByName( "TextPortionType" ) )
+ return true;
+
+#if OSL_DEBUG_LEVEL > 0
+ OUString sInfo;
+ OUString sPortionType;
+ if( xPortionPropSet->getPropertyValue( "TextPortionType" ) >>= sPortionType )
+ {
+ sInfo = "type: " + sPortionType + "; ";
+ }
+#endif
+ msPageCount = "";
+ msDateTimeType = "";
+ msTextFieldType = "";
+ if( xPortionTextRange.is() )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ sInfo += "content: " + xPortionTextRange->getString() + "; ";
+#endif
+ mrCurrentTextPortion.set( xPortionTextRange );
+
+ Reference < XPropertySet > xRangePropSet( xPortionTextRange, UNO_QUERY );
+ if( xRangePropSet.is() && xRangePropSet->getPropertySetInfo()->hasPropertyByName( "TextField" ) )
+ {
+ Reference < XTextField > xTextField( xRangePropSet->getPropertyValue( "TextField" ), UNO_QUERY );
+ if( xTextField.is() )
+ {
+ static const OUStringLiteral sServicePrefix(u"com.sun.star.text.textfield.");
+ static const OUStringLiteral sPresentationServicePrefix(u"com.sun.star.presentation.TextField.");
+
+ Reference< XServiceInfo > xService( xTextField, UNO_QUERY );
+ const Sequence< OUString > aServices = xService->getSupportedServiceNames();
+
+ const OUString* pNames = aServices.getConstArray();
+ sal_Int32 nCount = aServices.getLength();
+
+ OUString sFieldName; // service name postfix of current field
+
+ // search for TextField service name
+ while( nCount-- )
+ {
+ if ( pNames->matchIgnoreAsciiCase( sServicePrefix ) )
+ {
+ // TextField found => postfix is field type!
+ sFieldName = pNames->copy( sServicePrefix.getLength() );
+ break;
+ }
+ else if( pNames->startsWith( sPresentationServicePrefix ) )
+ {
+ // TextField found => postfix is field type!
+ sFieldName = pNames->copy( sPresentationServicePrefix.getLength() );
+ break;
+ }
+
+ ++pNames;
+ }
+
+ msTextFieldType = sFieldName;
+#if OSL_DEBUG_LEVEL > 0
+ sInfo += "text field type: " + sFieldName + "; content: " + xTextField->getPresentation( /* show command: */ false ) + "; ";
+#endif
+ // This case handle Date or Time text field inserted by the user
+ // on both page/master page. It doesn't handle the standard Date/Time field.
+ if( sFieldName == "DateTime" )
+ {
+ Reference<XPropertySet> xTextFieldPropSet(xTextField, UNO_QUERY);
+ if( xTextFieldPropSet.is() )
+ {
+ Reference<XPropertySetInfo> xPropSetInfo = xTextFieldPropSet->getPropertySetInfo();
+ if( xPropSetInfo.is() )
+ {
+ // The standard Date/Time field has no property.
+ // Trying to get a property value on such field would cause a runtime exception.
+ // So the hasPropertyByName check is needed.
+ bool bIsFixed = true;
+ if( xPropSetInfo->hasPropertyByName("IsFixed") && ( ( xTextFieldPropSet->getPropertyValue( "IsFixed" ) ) >>= bIsFixed ) && !bIsFixed )
+ {
+ bool bIsDate = true;
+ if( xPropSetInfo->hasPropertyByName("IsDate") && ( ( xTextFieldPropSet->getPropertyValue( "IsDate" ) ) >>= bIsDate ) )
+ {
+ msDateTimeType = OUString::createFromAscii( bIsDate ? "<date>" : "<time>" );
+ }
+ }
+ }
+ }
+ }
+ if( sFieldName == "DateTime" || sFieldName == "Header"
+ || sFieldName == "Footer" || sFieldName == "PageNumber"
+ || sFieldName == "PageName" )
+ {
+ mbIsPlaceholderShape = true;
+ }
+ else if (sFieldName == "PageCount")
+ {
+ msPageCount = xTextField->getPresentation( /* show command: */ false );
+ }
+ else
+ {
+ mbIsURLField = sFieldName == "URL";
+
+ if( mbIsURLField )
+ {
+ Reference<XPropertySet> xTextFieldPropSet(xTextField, UNO_QUERY);
+ if( xTextFieldPropSet.is() )
+ {
+ OUString sURL;
+ if( ( xTextFieldPropSet->getPropertyValue( sFieldName ) ) >>= sURL )
+ {
+#if OSL_DEBUG_LEVEL > 0
+ sInfo += "url: " + mrExport.GetRelativeReference( sURL );
+#endif
+ msUrl = mrExport.GetRelativeReference( sURL );
+ if( !msUrl.isEmpty() )
+ {
+ implRegisterInterface( xPortionTextRange );
+
+ const OUString& rTextPortionId = implGetValidIDFromInterface( Reference<XInterface>(xPortionTextRange, UNO_QUERY) );
+ if( !rTextPortionId.isEmpty() )
+ {
+ msHyperlinkIdList += rTextPortionId + " ";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextPortion" );
+ SvXMLElementExport aPortionElem( mrExport, XML_NAMESPACE_NONE, "desc", mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( sInfo );
+#endif
+ return true;
+}
+
+
+void SVGTextWriter::startTextShape()
+{
+ if( mpTextShapeElem )
+ {
+ OSL_FAIL( "SVGTextWriter::startTextShape: text shape already defined." );
+ }
+
+ {
+ mbIsTextShapeStarted = true;
+ maParentFont = vcl::Font();
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "SVGTextShape" );
+
+ // if text is rotated, set transform matrix at text element
+ const vcl::Font& rFont = mpVDev->GetFont();
+ if( rFont.GetOrientation() )
+ {
+ Point aRot( maTextPos );
+ OUString aTransform = "rotate(" +
+ OUString::number( rFont.GetOrientation().get() * -0.1 ) + " " +
+ OUString::number( aRot.X() ) + " " +
+ OUString::number( aRot.Y() ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform );
+ }
+
+ mpTextShapeElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, mbIWS ));
+ startTextParagraph();
+ }
+}
+
+
+void SVGTextWriter::endTextShape()
+{
+ endTextParagraph();
+ mrTextShape.clear();
+ mrParagraphEnumeration.clear();
+ mrCurrentTextParagraph.clear();
+ mpTextShapeElem.reset();
+ maTextOpacity.clear();
+ mbIsTextShapeStarted = false;
+ // these need to be invoked after the <text> element has been closed
+ implExportHyperlinkIds();
+ implWriteBulletChars();
+ implWriteEmbeddedBitmaps();
+
+}
+
+
+void SVGTextWriter::startTextParagraph()
+{
+ endTextParagraph();
+ nextParagraph();
+ if( mbIsNewListItem )
+ {
+ OUString sNumberingType;
+ switch( meNumberingType )
+ {
+ case NumberingType::CHAR_SPECIAL:
+ sNumberingType = "bullet-style";
+ break;
+ case NumberingType::BITMAP:
+ sNumberingType = "image-style";
+ break;
+ default:
+ sNumberingType = "number-style";
+ break;
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "ooo:numbering-type", sNumberingType );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "ListItem" );
+ }
+ else
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextParagraph" );
+ }
+ maParentFont = vcl::Font();
+ addFontAttributes( /* isTexTContainer: */ true );
+ mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ));
+
+ if( !mbIsListLevelStyleImage )
+ {
+ mbPositioningNeeded = true;
+ }
+}
+
+
+void SVGTextWriter::endTextParagraph()
+{
+ mrCurrentTextPortion.clear();
+ endTextPosition();
+ mbIsNewListItem = false;
+ mbIsListLevelStyleImage = false;
+ mbPositioningNeeded = false;
+ mpTextParagraphElem.reset();
+}
+
+
+void SVGTextWriter::startTextPosition( bool bExportX, bool bExportY )
+{
+ endTextPosition();
+ mnTextWidth = 0;
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "TextPosition" );
+ if( bExportX )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( maTextPos.X() ) );
+ if( bExportY )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( maTextPos.Y() ) );
+
+ mpTextPositionElem.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ) );
+}
+
+
+void SVGTextWriter::endTextPosition()
+{
+ mpTextPositionElem.reset();
+}
+
+bool SVGTextWriter::hasTextOpacity() const { return !maTextOpacity.isEmpty(); }
+
+void SVGTextWriter::implExportHyperlinkIds()
+{
+ if( !msHyperlinkIdList.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "HyperlinkIdList" );
+ SvXMLElementExport aDescElem( mrExport, XML_NAMESPACE_NONE, "desc", true, false );
+ mrExport.GetDocHandler()->characters( msHyperlinkIdList.trim() );
+ msHyperlinkIdList.clear();
+ }
+}
+
+
+void SVGTextWriter::implWriteBulletChars()
+{
+ if( maBulletListItemMap.empty() )
+ return;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletChars" );
+ SvXMLElementExport aGroupElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ OUString sId, sPosition, sScaling, sRefId;
+ for (auto const& bulletListItem : maBulletListItemMap)
+ {
+ // <g id="?" > (used by animations)
+ // As id we use the id of the text portion placeholder with prefix
+ // bullet-char-*
+ sId = "bullet-char-" + bulletListItem.first;
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletChar" );
+ SvXMLElementExport aBulletCharElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ // <g transform="translate(x,y)" >
+ {
+ const BulletListItemInfo& rInfo = bulletListItem.second;
+
+ // Add positioning attribute through a translation
+ sPosition = "translate(" +
+ OUString::number( rInfo.aPos.X() ) +
+ "," + OUString::number( rInfo.aPos.Y() ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "transform", sPosition );
+
+ mrAttributeWriter.AddPaintAttr( COL_TRANSPARENT, rInfo.aColor );
+
+ SvXMLElementExport aPositioningElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ if (mrExport.IsEmbeddedBulletGlyph(rInfo.cBulletChar))
+ {
+ // <use transform="scale(font-size)" xlink:ref="/" >
+ // Add size attribute through a scaling
+ sScaling = "scale(" + OUString::number( rInfo.aFont.GetFontHeight() ) +
+ "," + OUString::number( rInfo.aFont.GetFontHeight() )+ ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "transform", sScaling );
+
+ // Add ref attribute
+ sRefId = "#bullet-char-template-" +
+ OUString::number( rInfo.cBulletChar );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ }
+ else
+ {
+ // <path d="...">
+ tools::PolyPolygon aPolyPolygon;
+ OUString aStr(rInfo.cBulletChar);
+ mpVDev->Push(vcl::PushFlags::FONT);
+ mpVDev->SetFont(rInfo.aFont);
+ if (mpVDev->GetTextOutline(aPolyPolygon, aStr))
+ {
+ OUString aPathString(SVGActionWriter::GetPathString(aPolyPolygon, false));
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, "d", aPathString);
+ SvXMLElementExport aPath(mrExport, XML_NAMESPACE_NONE, "path", true, true);
+ }
+ mpVDev->Pop();
+ }
+ } // close aPositioningElem
+ }
+
+ // clear the map
+ maBulletListItemMap.clear();
+}
+
+
+template< typename MetaBitmapActionType >
+void SVGTextWriter::writeBitmapPlaceholder( const MetaBitmapActionType* pAction )
+{
+ // text position element
+ const Point& rPos = pAction->GetPoint();
+ implMap( rPos, maTextPos );
+ startTextPosition();
+ mbPositioningNeeded = true;
+ if( mbIsNewListItem )
+ {
+ mbIsNewListItem = false;
+ mbIsListLevelStyleImage = false;
+ }
+
+ // bitmap placeholder element
+ BitmapChecksum nId = SVGActionWriter::GetChecksum( pAction );
+ OUString sId = "bitmap-placeholder(" + msShapeId + "." +
+ OUString::number( nId ) + ")";
+
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BitmapPlaceholder" );
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ }
+ endTextPosition();
+}
+
+
+void SVGTextWriter::implWriteEmbeddedBitmaps()
+{
+ if( !(mpTextEmbeddedBitmapMtf && mpTextEmbeddedBitmapMtf->GetActionSize()) )
+ return;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBitmaps" );
+ SvXMLElementExport aEmbBitmapGroupElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ const GDIMetaFile& rMtf = *mpTextEmbeddedBitmapMtf;
+
+ BitmapChecksum nId, nChecksum = 0;
+ Point aPt;
+ Size aSz;
+ size_t nCount = rMtf.GetActionSize();
+ for( size_t nCurAction = 0; nCurAction < nCount; nCurAction++ )
+ {
+
+ const MetaAction* pAction = rMtf.GetAction( nCurAction );
+ const MetaActionType nType = pAction->GetType();
+
+ switch( nType )
+ {
+ case MetaActionType::BMPSCALE:
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+ // The conversion to BitmapEx is needed since at the point
+ // where the bitmap is actually exported a Bitmap object is
+ // converted to BitmapEx before computing the checksum used
+ // to generate the <image> element id.
+ // (See GetBitmapChecksum in svgexport.cxx)
+ nChecksum = BitmapEx( pA->GetBitmap() ).GetChecksum();
+ aPt = pA->GetPoint();
+ aSz = pA->GetSize();
+ }
+ break;
+ case MetaActionType::BMPEXSCALE:
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+ nChecksum = pA->GetBitmapEx().GetChecksum();
+ aPt = pA->GetPoint();
+ aSz = pA->GetSize();
+ }
+ break;
+ default: break;
+ }
+
+ // <g id="?" > (used by animations)
+ {
+ // embedded bitmap id
+ nId = SVGActionWriter::GetChecksum( pAction );
+ OUString sId = "embedded-bitmap(" + msShapeId + "." + OUString::number( nId ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "EmbeddedBitmap" );
+
+ SvXMLElementExport aEmbBitmapElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ // <use x="?" y="?" xlink:ref="?" >
+ {
+ // referenced bitmap template
+ OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")";
+
+ Point aPoint;
+ Size aSize;
+ implMap( aPt, aPoint );
+ implMap( aSz, aSize );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPoint.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPoint.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ }
+ } // close aEmbBitmapElem
+ }
+}
+
+
+void SVGTextWriter::writeTextPortion( const Point& rPos,
+ const OUString& rText )
+{
+ if( rText.isEmpty() )
+ return;
+
+ bool bStandAloneTextPortion = false;
+ if( !isTextShapeStarted() )
+ {
+ bStandAloneTextPortion = true;
+ startTextShape();
+ }
+
+ mbLineBreak = false;
+
+ if( !mbIsNewListItem || mbIsListLevelStyleImage )
+ {
+ bool bNotSync = true;
+ OUString sContent;
+ sal_Int32 nStartPos;
+ while( bNotSync )
+ {
+ if( mnLeftTextPortionLength <= 0 || !mrCurrentTextPortion.is() )
+ {
+ if( !nextTextPortion() )
+ break;
+ else
+ {
+ sContent = mrCurrentTextPortion->getString();
+ if( mbIsURLField && sContent.isEmpty() )
+ {
+ Reference < XPropertySet > xPropSet( mrCurrentTextPortion, UNO_QUERY );
+ Reference < XTextField > xTextField( xPropSet->getPropertyValue( "TextField" ), UNO_QUERY );
+ sContent = xTextField->getPresentation( /* show command: */ false );
+ if( sContent.isEmpty() )
+ OSL_FAIL( "SVGTextWriter::writeTextPortion: content of URL TextField is empty." );
+ }
+ mnLeftTextPortionLength = sContent.getLength();
+ }
+ }
+ else
+ {
+ sContent = mrCurrentTextPortion->getString();
+ }
+
+ nStartPos = sContent.getLength() - mnLeftTextPortionLength;
+ if( nStartPos < 0 ) nStartPos = 0;
+ mnLeftTextPortionLength -= rText.getLength();
+
+ if( sContent.isEmpty() )
+ continue;
+ if( sContent == "\n" )
+ mbLineBreak = true;
+ else if (sContent == "\t")
+ {
+ // Need to emit position for the next text portion after a tab, otherwise the tab
+ // would appear as if it has 0 width.
+ mbPositioningNeeded = true;
+ }
+ if( sContent.match( rText, nStartPos ) )
+ bNotSync = false;
+ }
+ }
+
+ assert(mpVDev); //invalid virtual device
+
+#if 0
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+
+ bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE);
+
+ if( true || !bTextSpecial )
+ {
+ implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() );
+ }
+ else
+ {
+ // to be implemented
+ }
+#else
+ implWriteTextPortion( rPos, rText, mpVDev->GetTextColor() );
+#endif
+
+ if( bStandAloneTextPortion )
+ {
+ endTextShape();
+ }
+}
+
+
+void SVGTextWriter::implWriteTextPortion( const Point& rPos,
+ const OUString& rText,
+ Color aTextColor )
+{
+ Point aPos;
+ Point aBaseLinePos( rPos );
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+ const vcl::Font& rFont = mpVDev->GetFont();
+
+ if( rFont.GetAlignment() == ALIGN_TOP )
+ aBaseLinePos.AdjustY(aMetric.GetAscent() );
+ else if( rFont.GetAlignment() == ALIGN_BOTTOM )
+ aBaseLinePos.AdjustY( -(aMetric.GetDescent()) );
+
+ implMap( rPos, aPos );
+
+ if( mbPositioningNeeded )
+ {
+ mbPositioningNeeded = false;
+ maTextPos.setX( aPos.X() );
+ maTextPos.setY( aPos.Y() );
+ startTextPosition();
+ }
+ else if( maTextPos.Y() != aPos.Y() )
+ {
+ // In case the text position moved backward we could have a line break
+ // so we end the current line and start a new one.
+ if( mbLineBreak || ( ( maTextPos.X() + mnTextWidth ) > aPos.X() ) )
+ {
+ mbLineBreak = false;
+ maTextPos.setX( aPos.X() );
+ maTextPos.setY( aPos.Y() );
+ startTextPosition();
+ }
+ else // superscript, subscript, list item numbering
+ {
+ maTextPos.setY( aPos.Y() );
+ startTextPosition( false /* do not export x attribute */ );
+ }
+ }
+ // we are dealing with a bullet, so set up this for the next text portion
+ if( mbIsNewListItem )
+ {
+ mbIsNewListItem = false;
+ mbPositioningNeeded = true;
+
+ if( meNumberingType == NumberingType::CHAR_SPECIAL )
+ {
+ // Create an id for the current text portion
+ implRegisterInterface( mrCurrentTextParagraph );
+
+ // Add the needed info to the BulletListItemMap
+ OUString sId = implGetValidIDFromInterface( Reference<XInterface>(mrCurrentTextParagraph, UNO_QUERY) );
+ if( !sId.isEmpty() )
+ {
+ sId += ".bp";
+ BulletListItemInfo& aBulletListItemInfo = maBulletListItemMap[ sId ];
+ aBulletListItemInfo.aFont = rFont;
+ aBulletListItemInfo.aColor = aTextColor;
+ aBulletListItemInfo.aPos = maTextPos;
+ aBulletListItemInfo.cBulletChar = mcBulletChar;
+
+ // Make this text portion a bullet placeholder
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", sId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "BulletPlaceholder" );
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ return;
+ }
+ }
+ }
+
+ const OUString& rTextPortionId = implGetValidIDFromInterface( Reference<XInterface>(mrCurrentTextPortion, UNO_QUERY) );
+ if( !rTextPortionId.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "id", rTextPortionId );
+ }
+
+ if( mbIsPlaceholderShape )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PlaceholderText" );
+ }
+
+ addFontAttributes( /* isTexTContainer: */ false );
+
+ if (!maTextOpacity.isEmpty())
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, "fill-opacity", maTextOpacity);
+ }
+
+ mrAttributeWriter.AddPaintAttr( COL_TRANSPARENT, aTextColor );
+
+ // <a> tag for link should be the innermost tag, inside <tspan>
+ if( !mbIsPlaceholderShape && mbIsURLField && !msUrl.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "UrlField" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, msUrl );
+
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, msUrl );
+ {
+ SvXMLElementExport aSVGAElem( mrExport, XML_NAMESPACE_NONE, "a", mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( rText );
+ }
+ }
+ else if ( !msPageCount.isEmpty() )
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PageCount" );
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( msPageCount );
+ }
+ // This case handle Date or Time text field inserted by the user
+ // on both page/master page. It doesn't handle the standard Date/Time field.
+ else if ( !msDateTimeType.isEmpty() )
+ {
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( msDateTimeType );
+ }
+ else if( mbIsPlaceholderShape && rText.startsWith("<") && rText.endsWith(">") )
+ {
+ OUString sContent;
+ if( msTextFieldType == "PageNumber" )
+ sContent = "<number>";
+ else if( msTextFieldType == "PageName" )
+ sContent = "<slide-name>";
+ else
+ sContent = rText;
+
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( sContent );
+ }
+ else
+ {
+ // Without the following attribute Google Chrome does not render leading spaces
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "style", "white-space: pre" );
+
+ SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
+ mrExport.GetDocHandler()->characters( rText );
+ }
+
+ mnTextWidth += mpVDev->GetTextWidth( rText );
+}
+
+
+SVGActionWriter::SVGActionWriter( SVGExport& rExport, SVGFontExport& rFontExport ) :
+ mnCurGradientId( 1 ),
+ mnCurMaskId( 1 ),
+ mnCurPatternId( 1 ),
+ mnCurClipPathId( 1 ),
+ mrExport( rExport ),
+ maContextHandler(),
+ mrCurrentState( maContextHandler.getCurrentState() ),
+ maAttributeWriter( rExport, rFontExport, mrCurrentState ),
+ maTextWriter(rExport, maAttributeWriter, *this),
+ mpVDev(VclPtr<VirtualDevice>::Create()),
+ mbClipAttrChanged( false ),
+ mbIsPlaceholderShape( false ),
+ mpEmbeddedBitmapsMap( nullptr ),
+ mbIsPreview( false )
+{
+ mpVDev->EnableOutput( false );
+ maTargetMapMode = MapMode(MapUnit::Map100thMM);
+ maTextWriter.setVirtualDevice( mpVDev, maTargetMapMode );
+}
+
+
+SVGActionWriter::~SVGActionWriter()
+{
+ mpVDev.disposeAndClear();
+}
+
+
+tools::Long SVGActionWriter::ImplMap( sal_Int32 nVal ) const
+{
+ Size aSz( nVal, nVal );
+
+ return ImplMap( aSz, aSz ).Width();
+}
+
+
+Point& SVGActionWriter::ImplMap( const Point& rPt, Point& rDstPt ) const
+{
+ rDstPt = OutputDevice::LogicToLogic( rPt, mpVDev->GetMapMode(), maTargetMapMode );
+ return rDstPt;
+}
+
+
+Size& SVGActionWriter::ImplMap( const Size& rSz, Size& rDstSz ) const
+{
+ rDstSz = OutputDevice::LogicToLogic( rSz, mpVDev->GetMapMode(), maTargetMapMode );
+ return rDstSz;
+}
+
+
+void SVGActionWriter::ImplMap( const tools::Rectangle& rRect, tools::Rectangle& rDstRect ) const
+{
+ Point aTL( rRect.TopLeft() );
+ Size aSz( rRect.GetSize() );
+
+ rDstRect = tools::Rectangle( ImplMap( aTL, aTL ), ImplMap( aSz, aSz ) );
+}
+
+
+tools::Polygon& SVGActionWriter::ImplMap( const tools::Polygon& rPoly, tools::Polygon& rDstPoly ) const
+{
+ rDstPoly = tools::Polygon( rPoly.GetSize() );
+
+ for( sal_uInt16 i = 0, nSize = rPoly.GetSize(); i < nSize; ++i )
+ {
+ ImplMap( rPoly[ i ], rDstPoly[ i ] );
+ rDstPoly.SetFlags( i, rPoly.GetFlags( i ) );
+ }
+
+ return rDstPoly;
+}
+
+
+tools::PolyPolygon& SVGActionWriter::ImplMap( const tools::PolyPolygon& rPolyPoly, tools::PolyPolygon& rDstPolyPoly ) const
+{
+ tools::Polygon aPoly;
+
+ rDstPolyPoly = tools::PolyPolygon();
+
+ for( sal_uInt16 i = 0, nCount = rPolyPoly.Count(); i < nCount; ++i )
+ {
+ rDstPolyPoly.Insert( ImplMap( rPolyPoly[ i ], aPoly ) );
+ }
+
+ return rDstPolyPoly;
+}
+
+
+OUString SVGActionWriter::GetPathString( const tools::PolyPolygon& rPolyPoly, bool bLine )
+{
+ OUStringBuffer aPathData;
+ static const OUStringLiteral aBlank( u" " );
+ static const OUStringLiteral aComma( u"," );
+ Point aPolyPoint;
+
+ for( tools::Long i = 0, nCount = rPolyPoly.Count(); i < nCount; i++ )
+ {
+ const tools::Polygon& rPoly = rPolyPoly[ static_cast<sal_uInt16>(i) ];
+ sal_uInt16 n = 1, nSize = rPoly.GetSize();
+
+ if( nSize > 1 )
+ {
+ aPolyPoint = rPoly[ 0 ];
+ aPathData.append("M " +
+ OUString::number( aPolyPoint.X() ) +
+ aComma +
+ OUString::number( aPolyPoint.Y() ));
+
+ char nCurrentMode = 0;
+ const bool bClose(!bLine || rPoly[0] == rPoly[nSize - 1]);
+ while( n < nSize )
+ {
+ aPathData.append(aBlank);
+
+ if ( ( rPoly.GetFlags( n ) == PolyFlags::Control ) && ( ( n + 2 ) < nSize ) )
+ {
+ if ( nCurrentMode != 'C' )
+ {
+ nCurrentMode = 'C';
+ aPathData.append("C ");
+ }
+ for ( int j = 0; j < 3; j++ )
+ {
+ if ( j )
+ aPathData.append(aBlank);
+
+ aPolyPoint = rPoly[ n++ ];
+ aPathData.append( OUString::number(aPolyPoint.X()) +
+ aComma +
+ OUString::number( aPolyPoint.Y() ) );
+ }
+ }
+ else
+ {
+ if ( nCurrentMode != 'L' )
+ {
+ nCurrentMode = 'L';
+ aPathData.append("L ");
+ }
+
+ aPolyPoint = rPoly[ n++ ];
+ aPathData.append( OUString::number(aPolyPoint.X()) +
+ aComma +
+ OUString::number(aPolyPoint.Y()) );
+ }
+ }
+
+ if(bClose)
+ aPathData.append(" Z");
+
+ if( i < ( nCount - 1 ) )
+ aPathData.append(aBlank);
+ }
+ }
+
+ return aPathData.makeStringAndClear();
+}
+
+BitmapChecksum SVGActionWriter::GetChecksum( const MetaAction* pAction )
+{
+ GDIMetaFile aMtf;
+ MetaAction* pA = const_cast<MetaAction*>(pAction);
+ aMtf.AddAction( pA );
+ return SvmWriter::GetChecksum( aMtf );
+}
+
+void SVGActionWriter::SetEmbeddedBitmapRefs( const MetaBitmapActionMap* pEmbeddedBitmapsMap )
+{
+ if (pEmbeddedBitmapsMap)
+ mpEmbeddedBitmapsMap = pEmbeddedBitmapsMap;
+ else
+ OSL_FAIL( "SVGActionWriter::SetEmbeddedBitmapRefs: passed pointer is null" );
+}
+
+void SVGActionWriter::ImplWriteLine( const Point& rPt1, const Point& rPt2,
+ const Color* pLineColor )
+{
+ Point aPt1, aPt2;
+
+ ImplMap( rPt1, aPt1 );
+ ImplMap( rPt2, aPt2 );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aPt1.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aPt1.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aPt2.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aPt2.Y() ) );
+
+ if( pLineColor )
+ {
+ // !!! mrExport.AddAttribute( XML_NAMESPACE_NONE, ... )
+ OSL_FAIL( "SVGActionWriter::ImplWriteLine: Line color not implemented" );
+ }
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "line", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteRect( const tools::Rectangle& rRect, tools::Long nRadX, tools::Long nRadY )
+{
+ tools::Rectangle aRect;
+
+ ImplMap( rRect, aRect );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aRect.Left() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aRect.Top() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aRect.GetWidth() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aRect.GetHeight() ) );
+
+ if( nRadX )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRX, OUString::number( ImplMap( nRadX ) ) );
+
+ if( nRadY )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRY, OUString::number( ImplMap( nRadY ) ) );
+
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "rect", true, true );
+}
+
+
+void SVGActionWriter::ImplWriteEllipse( const Point& rCenter, tools::Long nRadX, tools::Long nRadY )
+{
+ Point aCenter;
+
+ ImplMap( rCenter, aCenter );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCX, OUString::number( aCenter.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrCY, OUString::number( aCenter.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRX, OUString::number( ImplMap( nRadX ) ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrRY, OUString::number( ImplMap( nRadY ) ) );
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "ellipse", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplAddLineAttr( const LineInfo &rAttrs )
+{
+ if ( rAttrs.IsDefault() )
+ return;
+
+ sal_Int32 nStrokeWidth = ImplMap( rAttrs.GetWidth() );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStrokeWidth,
+ OUString::number( nStrokeWidth ) );
+ // support for LineJoint
+ switch(rAttrs.GetLineJoin())
+ {
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "miter");
+ break;
+ }
+ case basegfx::B2DLineJoin::Bevel:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "bevel");
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "round");
+ break;
+ }
+ }
+
+ // support for LineCap
+ switch(rAttrs.GetLineCap())
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ // butt is Svg default, so no need to write until the exporter might write styles.
+ // If this happens, activate here
+ // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "butt");
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "round");
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "square");
+ break;
+ }
+ }
+
+}
+
+
+void SVGActionWriter::ImplWritePolyPolygon( const tools::PolyPolygon& rPolyPoly, bool bLineOnly,
+ bool bApplyMapping )
+{
+ tools::PolyPolygon aPolyPoly;
+
+ if( bApplyMapping )
+ ImplMap( rPolyPoly, aPolyPoly );
+ else
+ aPolyPoly = rPolyPoly;
+
+ // add path data attribute
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "d", GetPathString( aPolyPoly, bLineOnly ) );
+
+ {
+ // write polyline/polygon element
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "path", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteShape( const SVGShapeDescriptor& rShape )
+{
+ tools::PolyPolygon aPolyPoly;
+
+ ImplMap( rShape.maShapePolyPoly, aPolyPoly );
+
+ const bool bLineOnly
+ = (rShape.maShapeFillColor == COL_TRANSPARENT) && (!rShape.mapShapeGradient);
+ tools::Rectangle aBoundRect( aPolyPoly.GetBoundRect() );
+
+ maAttributeWriter.AddPaintAttr( rShape.maShapeLineColor, rShape.maShapeFillColor, &aBoundRect, rShape.mapShapeGradient.get() );
+
+ if( !rShape.maId.isEmpty() )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, rShape.maId );
+
+ if( rShape.mnStrokeWidth )
+ {
+ sal_Int32 nStrokeWidth = ImplMap( rShape.mnStrokeWidth );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStrokeWidth, OUString::number( nStrokeWidth ) );
+ }
+
+ // support for LineJoin
+ switch(rShape.maLineJoin)
+ {
+ case basegfx::B2DLineJoin::NONE:
+ case basegfx::B2DLineJoin::Miter:
+ {
+ // miter is Svg default, so no need to write until the exporter might write styles.
+ // If this happens, activate here
+ // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "miter");
+ break;
+ }
+ case basegfx::B2DLineJoin::Bevel:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "bevel");
+ break;
+ }
+ case basegfx::B2DLineJoin::Round:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, "round");
+ break;
+ }
+ }
+
+ // support for LineCap
+ switch(rShape.maLineCap)
+ {
+ default: /* css::drawing::LineCap_BUTT */
+ {
+ // butt is Svg default, so no need to write until the exporter might write styles.
+ // If this happens, activate here
+ // mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "butt");
+ break;
+ }
+ case css::drawing::LineCap_ROUND:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "round");
+ break;
+ }
+ case css::drawing::LineCap_SQUARE:
+ {
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, "square");
+ break;
+ }
+ }
+
+ if( !rShape.maDashArray.empty() )
+ {
+ OUStringBuffer aDashArrayStr;
+
+ for( size_t k = 0; k < rShape.maDashArray.size(); ++k )
+ {
+ const sal_Int32 nDash = ImplMap( FRound( rShape.maDashArray[ k ] ) );
+
+ if( k )
+ aDashArrayStr.append(",");
+
+ aDashArrayStr.append( nDash );
+ }
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "stroke-dasharray", aDashArrayStr.makeStringAndClear() );
+ }
+
+ ImplWritePolyPolygon( aPolyPoly, bLineOnly, false );
+}
+
+
+
+void SVGActionWriter::ImplCreateClipPathDef( const tools::PolyPolygon& rPolyPoly )
+{
+ OUString aClipPathId = aPrefixClipPathId + OUString::number( mnCurClipPathId++ );
+
+ SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aClipPathId );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "clipPathUnits", "userSpaceOnUse" );
+ SvXMLElementExport aElemClipPath( mrExport, XML_NAMESPACE_NONE, "clipPath", true, true );
+
+ ImplWritePolyPolygon(rPolyPoly, false);
+ }
+}
+
+void SVGActionWriter::ImplStartClipRegion(sal_Int32 nClipPathId)
+{
+ assert(!mpCurrentClipRegionElem);
+
+ if (nClipPathId == 0)
+ return;
+
+ OUString aUrl = OUString::Concat("url(#") + aPrefixClipPathId + OUString::number( nClipPathId ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "clip-path", aUrl );
+ mpCurrentClipRegionElem.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true ) );
+}
+
+void SVGActionWriter::ImplEndClipRegion()
+{
+ if (mpCurrentClipRegionElem)
+ {
+ mpCurrentClipRegionElem.reset();
+ }
+}
+
+void SVGActionWriter::ImplWriteClipPath( const tools::PolyPolygon& rPolyPoly )
+{
+ ImplEndClipRegion();
+
+ if( rPolyPoly.Count() == 0 )
+ return;
+
+ ImplCreateClipPathDef(rPolyPoly);
+ mrCurrentState.nRegionClipPathId = mnCurClipPathId - 1;
+ ImplStartClipRegion( mrCurrentState.nRegionClipPathId );
+}
+
+void SVGActionWriter::ImplWritePattern( const tools::PolyPolygon& rPolyPoly,
+ const Hatch* pHatch,
+ const Gradient* pGradient,
+ sal_uInt32 nWriteFlags )
+{
+ if( !rPolyPoly.Count() )
+ return;
+
+ SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ OUString aPatternId = "pattern" + OUString::number( mnCurPatternId++ );
+
+ {
+ SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aPatternId );
+
+ tools::Rectangle aRect;
+ ImplMap( rPolyPoly.GetBoundRect(), aRect );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aRect.Left() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aRect.Top() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aRect.GetWidth() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aRect.GetHeight() ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "patternUnits", OUString( "userSpaceOnUse") );
+
+ {
+ SvXMLElementExport aElemPattern( mrExport, XML_NAMESPACE_NONE, "pattern", true, true );
+
+ // The origin of a pattern is positioned at (aRect.Left(), aRect.Top()).
+ // So we need to adjust the pattern coordinate.
+ OUString aTransform = "translate(" +
+ OUString::number( -aRect.Left() ) +
+ "," + OUString::number( -aRect.Top() ) +
+ ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform );
+
+ {
+ SvXMLElementExport aElemG2( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ GDIMetaFile aTmpMtf;
+ if( pHatch )
+ {
+ mpVDev->AddHatchActions( rPolyPoly, *pHatch, aTmpMtf );
+ }
+ else if ( pGradient )
+ {
+ Gradient aGradient(*pGradient);
+ aGradient.AddGradientActions( rPolyPoly.GetBoundRect(), aTmpMtf );
+ }
+
+ ImplWriteActions( aTmpMtf, nWriteFlags, "" );
+ }
+ }
+ }
+
+ OUString aPatternStyle = "fill:url(#" + aPatternId + ")";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aPatternStyle );
+ ImplWritePolyPolygon( rPolyPoly, false );
+}
+
+
+void SVGActionWriter::ImplWriteGradientEx( const tools::PolyPolygon& rPolyPoly, const Gradient& rGradient,
+ sal_uInt32 nWriteFlags)
+{
+ if ( rGradient.GetStyle() == GradientStyle::Linear ||
+ rGradient.GetStyle() == GradientStyle::Axial )
+ {
+ ImplWriteGradientLinear( rPolyPoly, rGradient );
+ }
+ else
+ {
+ ImplWritePattern( rPolyPoly, nullptr, &rGradient, nWriteFlags );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly,
+ const Gradient& rGradient )
+{
+ if( !rPolyPoly.Count() )
+ return;
+
+ SvXMLElementExport aElemG( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true );
+
+ OUString aGradientId = "gradient" + OUString::number( mnCurGradientId++ );
+
+ {
+ SvXMLElementExport aElemDefs( mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrId, aGradientId );
+ {
+ tools::Rectangle aTmpRect, aRect;
+ Point aTmpCenter, aCenter;
+
+ rGradient.GetBoundRect( rPolyPoly.GetBoundRect(), aTmpRect, aTmpCenter );
+ ImplMap( aTmpRect, aRect );
+ ImplMap( aTmpCenter, aCenter );
+ const Degree10 nAngle = rGradient.GetAngle() % 3600_deg10;
+
+ tools::Polygon aPoly( 2 );
+ // Setting x value of a gradient vector to rotation center to
+ // place a gradient vector in a target polygon.
+ // This would help editing it in SVG editors like inkscape.
+ aPoly[ 0 ].setX( aCenter.X() );
+ aPoly[ 1 ].setX( aCenter.X() );
+ aPoly[ 0 ].setY( aRect.Top() );
+ aPoly[ 1 ].setY( aRect.Bottom() );
+ aPoly.Rotate( aCenter, nAngle );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX1, OUString::number( aPoly[ 0 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY1, OUString::number( aPoly[ 0 ].Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX2, OUString::number( aPoly[ 1 ].X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY2, OUString::number( aPoly[ 1 ].Y() ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrGradientUnits,
+ OUString( "userSpaceOnUse" ) );
+ }
+
+ {
+ SvXMLElementExport aElemLinearGradient( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true );
+
+ const Color aStartColor = ImplGetColorWithIntensity( rGradient.GetStartColor(), rGradient.GetStartIntensity() );
+ const Color aEndColor = ImplGetColorWithIntensity( rGradient.GetEndColor(), rGradient.GetEndIntensity() );
+ double fBorderOffset = rGradient.GetBorder() / 100.0;
+ const sal_uInt16 nSteps = rGradient.GetSteps();
+ if( rGradient.GetStyle() == GradientStyle::Linear )
+ {
+ // Emulate non-smooth gradient
+ if( 0 < nSteps && nSteps < 100 )
+ {
+ double fOffsetStep = ( 1.0 - fBorderOffset ) / static_cast<double>(nSteps);
+ for( sal_uInt16 i = 0; i < nSteps; i++ ) {
+ Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep );
+ aColor = ImplGetGradientColor( aStartColor, aEndColor, ( i + 1 ) / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + ( i + 1 ) * fOffsetStep );
+ }
+ }
+ else
+ {
+ ImplWriteGradientStop( aStartColor, fBorderOffset );
+ ImplWriteGradientStop( aEndColor, 1.0 );
+ }
+ }
+ else
+ {
+ fBorderOffset /= 2;
+ // Emulate non-smooth gradient
+ if( 0 < nSteps && nSteps < 100 )
+ {
+ double fOffsetStep = ( 0.5 - fBorderOffset ) / static_cast<double>(nSteps);
+ // Upper half
+ for( sal_uInt16 i = 0; i < nSteps; i++ )
+ {
+ Color aColor = ImplGetGradientColor( aEndColor, aStartColor, i / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep );
+ aColor = ImplGetGradientColor( aEndColor, aStartColor, (i + 1 ) / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, fBorderOffset + i * fOffsetStep );
+ }
+ // Lower half
+ for( sal_uInt16 i = 0; i < nSteps; i++ )
+ {
+ Color aColor = ImplGetGradientColor( aStartColor, aEndColor, i / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep );
+ aColor = ImplGetGradientColor( aStartColor, aEndColor, (i + 1 ) / static_cast<double>(nSteps) );
+ ImplWriteGradientStop( aColor, 0.5 + (i + 1) * fOffsetStep );
+ }
+ }
+ else
+ {
+ ImplWriteGradientStop( aEndColor, fBorderOffset );
+ ImplWriteGradientStop( aStartColor, 0.5 );
+ ImplWriteGradientStop( aEndColor, 1.0 - fBorderOffset );
+ }
+ }
+ }
+ }
+
+ OUString aGradientStyle = "fill:url(#" + aGradientId + ")";
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aGradientStyle );
+ ImplWritePolyPolygon( rPolyPoly, false );
+}
+
+
+void SVGActionWriter::ImplWriteGradientStop( const Color& rColor, double fOffset )
+{
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrOffset, OUString::number( fOffset ) );
+
+ OUString aStyle, aColor;
+ aStyle += "stop-color:";
+ SVGAttributeWriter::ImplGetColorStr ( rColor, aColor );
+ aStyle += aColor;
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle );
+ {
+ SvXMLElementExport aElemStartStop( mrExport, XML_NAMESPACE_NONE, aXMLElemStop, true, true );
+ }
+}
+
+
+Color SVGActionWriter::ImplGetColorWithIntensity( const Color& rColor,
+ sal_uInt16 nIntensity )
+{
+ sal_uInt8 nNewRed = static_cast<sal_uInt8>( static_cast<tools::Long>(rColor.GetRed()) * nIntensity / 100 );
+ sal_uInt8 nNewGreen = static_cast<sal_uInt8>( static_cast<tools::Long>(rColor.GetGreen()) * nIntensity / 100 );
+ sal_uInt8 nNewBlue = static_cast<sal_uInt8>( static_cast<tools::Long>(rColor.GetBlue()) * nIntensity / 100 );
+ return Color( nNewRed, nNewGreen, nNewBlue);
+}
+
+
+Color SVGActionWriter::ImplGetGradientColor( const Color& rStartColor,
+ const Color& rEndColor,
+ double fOffset )
+{
+ tools::Long nRedStep = rEndColor.GetRed() - rStartColor.GetRed();
+ tools::Long nNewRed = rStartColor.GetRed() + static_cast<tools::Long>( nRedStep * fOffset );
+ nNewRed = ( nNewRed < 0 ) ? 0 : ( nNewRed > 0xFF) ? 0xFF : nNewRed;
+
+ tools::Long nGreenStep = rEndColor.GetGreen() - rStartColor.GetGreen();
+ tools::Long nNewGreen = rStartColor.GetGreen() + static_cast<tools::Long>( nGreenStep * fOffset );
+ nNewGreen = ( nNewGreen < 0 ) ? 0 : ( nNewGreen > 0xFF) ? 0xFF : nNewGreen;
+
+ tools::Long nBlueStep = rEndColor.GetBlue() - rStartColor.GetBlue();
+ tools::Long nNewBlue = rStartColor.GetBlue() + static_cast<tools::Long>( nBlueStep * fOffset );
+ nNewBlue = ( nNewBlue < 0 ) ? 0 : ( nNewBlue > 0xFF) ? 0xFF : nNewBlue;
+
+ return Color( static_cast<sal_uInt8>(nNewRed), static_cast<sal_uInt8>(nNewGreen), static_cast<sal_uInt8>(nNewBlue) );
+}
+
+void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize,
+ const Gradient& rGradient, sal_uInt32 nWriteFlags,
+ OUString* pTextFillOpacity)
+{
+ OUString aStyle;
+ if (rGradient.GetStartColor() == rGradient.GetEndColor())
+ {
+ // Special case: constant alpha value.
+ const Color& rColor = rGradient.GetStartColor();
+ const double fOpacity = 1.0 - static_cast<double>(rColor.GetLuminance()) / 255;
+ if (pTextFillOpacity)
+ {
+ // Don't write anything, return what is a value suitable for <tspan fill-opacity="...">.
+ *pTextFillOpacity = OUString::number(fOpacity);
+ return;
+ }
+ else
+ {
+ aStyle = "opacity: " + OUString::number(fOpacity);
+ }
+ }
+ else
+ {
+ OUString aMaskId = "mask" + OUString::number(mnCurMaskId++);
+
+ {
+ SvXMLElementExport aElemDefs(mrExport, XML_NAMESPACE_NONE, aXMLElemDefs, true, true);
+
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrId, aMaskId);
+ {
+ SvXMLElementExport aElemMask(mrExport, XML_NAMESPACE_NONE, "mask", true, true);
+
+ const tools::PolyPolygon aPolyPolygon(tools::PolyPolygon(tools::Rectangle(rDestPt, rDestSize)));
+ Gradient aGradient(rGradient);
+
+ // swap gradient stops to adopt SVG mask
+ Color aTmpColor(aGradient.GetStartColor());
+ sal_uInt16 nTmpIntensity(aGradient.GetStartIntensity());
+ aGradient.SetStartColor(aGradient.GetEndColor());
+ aGradient.SetStartIntensity(aGradient.GetEndIntensity());
+ aGradient.SetEndColor(aTmpColor);
+ aGradient.SetEndIntensity(nTmpIntensity);
+
+ ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags);
+ }
+ }
+
+ aStyle = "mask:url(#" + aMaskId + ")";
+ }
+ mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStyle, aStyle);
+}
+
+void SVGActionWriter::ImplWriteMask(GDIMetaFile& rMtf, const Point& rDestPt, const Size& rDestSize,
+ const Gradient& rGradient, sal_uInt32 nWriteFlags)
+{
+ Point aSrcPt(rMtf.GetPrefMapMode().GetOrigin());
+ const Size aSrcSize(rMtf.GetPrefSize());
+ const double fScaleX
+ = aSrcSize.Width() ? static_cast<double>(rDestSize.Width()) / aSrcSize.Width() : 1.0;
+ const double fScaleY
+ = aSrcSize.Height() ? static_cast<double>(rDestSize.Height()) / aSrcSize.Height() : 1.0;
+ tools::Long nMoveX, nMoveY;
+
+ if (fScaleX != 1.0 || fScaleY != 1.0)
+ {
+ rMtf.Scale(fScaleX, fScaleY);
+ aSrcPt.setX(FRound(aSrcPt.X() * fScaleX));
+ aSrcPt.setY(FRound(aSrcPt.Y() * fScaleY));
+ }
+
+ nMoveX = rDestPt.X() - aSrcPt.X();
+ nMoveY = rDestPt.Y() - aSrcPt.Y();
+
+ if (nMoveX || nMoveY)
+ rMtf.Move(nMoveX, nMoveY);
+
+ {
+ std::unique_ptr<SvXMLElementExport> pElemG;
+ if (!maTextWriter.hasTextOpacity())
+ {
+ StartMask(rDestPt, rDestSize, rGradient, nWriteFlags);
+ pElemG.reset(
+ new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true));
+ }
+
+ mpVDev->Push();
+ ImplWriteActions( rMtf, nWriteFlags, "" );
+ mpVDev->Pop();
+ }
+}
+
+
+void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText,
+ o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth )
+{
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+
+ bool bTextSpecial = aMetric.IsShadow() || aMetric.IsOutline() || (aMetric.GetRelief() != FontRelief::NONE);
+
+ if( !bTextSpecial )
+ {
+ ImplWriteText( rPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ }
+ else
+ {
+ if( aMetric.GetRelief() != FontRelief::NONE )
+ {
+ Color aReliefColor( COL_LIGHTGRAY );
+ Color aTextColor( mpVDev->GetTextColor() );
+
+ if ( aTextColor == COL_BLACK )
+ aTextColor = COL_WHITE;
+
+ // coverity[copy_paste_error : FALSE] - aReliefColor depending on aTextColor is correct
+ if (aTextColor == COL_WHITE)
+ aReliefColor = COL_BLACK;
+
+
+ Point aPos( rPos );
+ Point aOffset( 6, 6 );
+
+ if ( aMetric.GetRelief() == FontRelief::Engraved )
+ {
+ aPos -= aOffset;
+ }
+ else
+ {
+ aPos += aOffset;
+ }
+
+ ImplWriteText( aPos, rText, pDXArray, nWidth, aReliefColor );
+ ImplWriteText( rPos, rText, pDXArray, nWidth, aTextColor );
+ }
+ else
+ {
+ if( aMetric.IsShadow() )
+ {
+ tools::Long nOff = 1 + ((aMetric.GetLineHeight()-24)/24);
+ if ( aMetric.IsOutline() )
+ nOff += 6;
+
+ Color aTextColor( mpVDev->GetTextColor() );
+ Color aShadowColor( COL_BLACK );
+
+ if ( (aTextColor == COL_BLACK) || (aTextColor.GetLuminance() < 8) )
+ aShadowColor = COL_LIGHTGRAY;
+
+ Point aPos( rPos );
+ aPos += Point( nOff, nOff );
+ ImplWriteText( aPos, rText, pDXArray, nWidth, aShadowColor );
+
+ if( !aMetric.IsOutline() )
+ {
+ ImplWriteText( rPos, rText, pDXArray, nWidth, aTextColor );
+ }
+ }
+
+ if( aMetric.IsOutline() )
+ {
+ Point aPos = rPos + Point( -6, -6 );
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +6, +6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( -6, +0);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( -6, +6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +0, +6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +0, -6);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +6, -1);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+ aPos = rPos + Point( +6, +0);
+ ImplWriteText( aPos, rText, pDXArray, nWidth, mpVDev->GetTextColor() );
+
+ ImplWriteText( rPos, rText, pDXArray, nWidth, COL_WHITE );
+ }
+ }
+ }
+}
+
+
+void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText,
+ o3tl::span<const sal_Int32> pDXArray, tools::Long nWidth,
+ Color aTextColor )
+{
+ sal_Int32 nLen = rText.getLength();
+ Size aNormSize;
+ Point aPos;
+ Point aBaseLinePos( rPos );
+ const FontMetric aMetric( mpVDev->GetFontMetric() );
+ const vcl::Font& rFont = mpVDev->GetFont();
+
+ if( rFont.GetAlignment() == ALIGN_TOP )
+ aBaseLinePos.AdjustY(aMetric.GetAscent() );
+ else if( rFont.GetAlignment() == ALIGN_BOTTOM )
+ aBaseLinePos.AdjustY( -(aMetric.GetDescent()) );
+
+ ImplMap( rPos, aPos );
+
+ std::vector<sal_Int32> aTmpArray(nLen);
+ // get text sizes
+ if( !pDXArray.empty() )
+ {
+ aNormSize = Size( mpVDev->GetTextWidth( rText ), 0 );
+ memcpy(aTmpArray.data(), pDXArray.data(), nLen * sizeof(sal_Int32));
+ }
+ else
+ {
+ aNormSize = Size( mpVDev->GetTextArray( rText, &aTmpArray ), 0 );
+ }
+ sal_Int32* pDX = aTmpArray.data();
+
+ // if text is rotated, set transform matrix at new g element
+ if( rFont.GetOrientation() )
+ {
+ Point aRot( aPos );
+ OUString aTransform = "rotate(" +
+ OUString::number( rFont.GetOrientation().get() * -0.1 ) + " " +
+ OUString::number( aRot.X() ) + " " +
+ OUString::number( aRot.Y() ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, aTransform );
+ }
+
+
+ maAttributeWriter.AddPaintAttr( COL_TRANSPARENT, aTextColor );
+
+ // for each line of text there should be at least one group element
+ SvXMLElementExport aSVGGElem( mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, false );
+
+ bool bIsPlaceholderField = false;
+
+ if( mbIsPlaceholderShape )
+ {
+ bIsPlaceholderField = rText.match( sPlaceholderTag );
+ // for a placeholder text field we export only one <text> svg element
+ if( bIsPlaceholderField )
+ {
+ OUString sCleanTextContent;
+ static const sal_Int32 nFrom = sPlaceholderTag.getLength();
+ if( rText.getLength() > nFrom )
+ {
+ sCleanTextContent = rText.copy( nFrom );
+ }
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "class", "PlaceholderText" );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPos.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) );
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false );
+ // At least for the single slide case we need really to export placeholder text
+ mrExport.GetDocHandler()->characters( sCleanTextContent );
+ }
+ }
+ }
+
+ if( !bIsPlaceholderField )
+ {
+ if( nLen > 1 )
+ {
+ aNormSize.setWidth( pDX[ nLen - 2 ] + mpVDev->GetTextWidth( OUString(rText[nLen - 1]) ) );
+
+ if( nWidth && aNormSize.Width() && ( nWidth != aNormSize.Width() ) )
+ {
+ tools::Long i;
+ const double fFactor = static_cast<double>(nWidth) / aNormSize.Width();
+
+ for( i = 0; i < ( nLen - 1 ); i++ )
+ pDX[ i ] = FRound( pDX[ i ] * fFactor );
+ }
+ else
+ {
+ css::uno::Reference< css::i18n::XBreakIterator > xBI( vcl::unohelper::CreateBreakIterator() );
+ const css::lang::Locale& rLocale = Application::GetSettings().GetLanguageTag().getLocale();
+ sal_Int32 nCurPos = 0, nLastPos = 0, nX = aPos.X();
+
+ // write single glyphs at absolute text positions
+ for( bool bCont = true; bCont; )
+ {
+ sal_Int32 nCount = 1;
+
+ nLastPos = nCurPos;
+ nCurPos = xBI->nextCharacters( rText, nCurPos, rLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL,
+ nCount, nCount );
+
+ nCount = nCurPos - nLastPos;
+ bCont = ( nCurPos < rText.getLength() ) && nCount;
+
+ if( nCount )
+ {
+ const OUString aGlyph( rText.copy( nLastPos, nCount ) );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( nX ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) );
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false );
+ mrExport.GetDocHandler()->characters( aGlyph );
+ }
+
+ if( bCont )
+ {
+ // #118796# do NOT access pDXArray, it may be zero (!)
+ sal_Int32 nDXWidth = pDX[ nCurPos - 1 ];
+ nDXWidth = ImplMap( nDXWidth );
+ nX = aPos.X() + nDXWidth;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPos.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPos.Y() ) );
+
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, aXMLElemText, true, false );
+ mrExport.GetDocHandler()->characters( rText );
+ }
+ }
+ }
+
+
+ if( mrExport.IsUseNativeTextDecoration() )
+ return;
+
+ if( rFont.GetStrikeout() == STRIKEOUT_NONE && rFont.GetUnderline() == LINESTYLE_NONE )
+ return;
+
+ tools::Polygon aPoly( 4 );
+ const tools::Long nLineHeight = std::max<tools::Long>( FRound( aMetric.GetLineHeight() * 0.05 ), 1 );
+
+ if( rFont.GetStrikeout() )
+ {
+ const tools::Long nYLinePos = aBaseLinePos.Y() - FRound( aMetric.GetAscent() * 0.26 );
+
+ aPoly[ 0 ].setX( aBaseLinePos.X() ); aPoly[ 0 ].setY( nYLinePos - ( nLineHeight >> 1 ) );
+ aPoly[ 1 ].setX( aBaseLinePos.X() + aNormSize.Width() - 1 ); aPoly[ 1 ].setY( aPoly[ 0 ].Y() );
+ aPoly[ 2 ].setX( aPoly[ 1 ].X() ); aPoly[ 2 ].setY( aPoly[ 0 ].Y() + nLineHeight - 1 );
+ aPoly[ 3 ].setX( aPoly[ 0 ].X() ); aPoly[ 3 ].setY( aPoly[ 2 ].Y() );
+
+ ImplWritePolyPolygon( aPoly, false );
+ }
+
+ if( rFont.GetUnderline() )
+ {
+ const tools::Long nYLinePos = aBaseLinePos.Y() + ( nLineHeight << 1 );
+
+ aPoly[ 0 ].setX( aBaseLinePos.X() ); aPoly[ 0 ].setY( nYLinePos - ( nLineHeight >> 1 ) );
+ aPoly[ 1 ].setX( aBaseLinePos.X() + aNormSize.Width() - 1 ); aPoly[ 1 ].setY( aPoly[ 0 ].Y() );
+ aPoly[ 2 ].setX( aPoly[ 1 ].X() ); aPoly[ 2 ].setY( aPoly[ 0 ].Y() + nLineHeight - 1 );
+ aPoly[ 3 ].setX( aPoly[ 0 ].X() ); aPoly[ 3 ].setY( aPoly[ 2 ].Y() );
+
+ ImplWritePolyPolygon( aPoly, false );
+ }
+}
+
+namespace
+{
+void GetGraphicFromXShape(const css::uno::Reference<css::drawing::XShape>* pShape, Graphic& rGraphic)
+{
+ if (!pShape)
+ {
+ return;
+ }
+
+ uno::Reference<beans::XPropertySet> xPropertySet(*pShape, uno::UNO_QUERY);
+ if (!xPropertySet.is())
+ {
+ return;
+ }
+
+ uno::Reference<graphic::XGraphic> xGraphic;
+ if (xPropertySet->getPropertySetInfo()->hasPropertyByName("Graphic"))
+ {
+ xPropertySet->getPropertyValue("Graphic") >>= xGraphic;
+ }
+ rGraphic= Graphic(xGraphic);
+}
+}
+
+void SVGActionWriter::ImplWriteBmp( const BitmapEx& rBmpEx,
+ const Point& rPt, const Size& rSz,
+ const Point& rSrcPt, const Size& rSrcSz,
+ const css::uno::Reference<css::drawing::XShape>* pShape )
+{
+ if( rBmpEx.IsEmpty() )
+ return;
+ if( mpEmbeddedBitmapsMap && !mpEmbeddedBitmapsMap->empty())
+ {
+ BitmapChecksum nChecksum = rBmpEx.GetChecksum();
+ if( mpEmbeddedBitmapsMap->find( nChecksum ) != mpEmbeddedBitmapsMap->end() )
+ {
+ // <use transform="translate(?) scale(?)" xlink:ref="?" >
+ OUString sTransform;
+
+ Point aPoint;
+ ImplMap( rPt, aPoint );
+ if( aPoint.X() != 0 || aPoint.Y() != 0 )
+ sTransform = "translate(" + OUString::number( aPoint.X() ) + ", " + OUString::number( aPoint.Y() ) + ")";
+
+ Size aSize;
+ ImplMap( rSz, aSize );
+
+ MapMode aSourceMode( MapUnit::MapPixel );
+ Size aPrefSize = OutputDevice::LogicToLogic( rSrcSz, aSourceMode, maTargetMapMode );
+ Fraction aFractionX( aSize.Width(), aPrefSize.Width() );
+ Fraction aFractionY( aSize.Height(), aPrefSize.Height() );
+ double scaleX = rtl_math_round( double(aFractionX), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ double scaleY = rtl_math_round( double(aFractionY), 3, rtl_math_RoundingMode::rtl_math_RoundingMode_Corrected );
+ if( !rtl_math_approxEqual( scaleX, 1.0 ) || !rtl_math_approxEqual( scaleY, 1.0 ) )
+ sTransform += " scale(" + OUString::number( double(aFractionX) ) + ", " + OUString::number( double(aFractionY) ) + ")";
+
+ if( !sTransform.isEmpty() )
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrTransform, sTransform );
+
+ // referenced bitmap template
+ OUString sRefId = "#bitmap(" + OUString::number( nChecksum ) + ")";
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ return;
+ }
+ }
+
+ BitmapEx aBmpEx( rBmpEx );
+ const tools::Rectangle aBmpRect( Point(), rBmpEx.GetSizePixel() );
+ const tools::Rectangle aSrcRect( rSrcPt, rSrcSz );
+
+ if( aSrcRect != aBmpRect )
+ aBmpEx.Crop( aSrcRect );
+
+ if( aBmpEx.IsEmpty() )
+ return;
+
+ SvMemoryStream aOStm( 65535, 65535 );
+
+ bool bCached = false;
+ Graphic aGraphic;
+ bool bJPG = false;
+ if (pShape)
+ {
+ GetGraphicFromXShape(pShape, aGraphic);
+ if (aGraphic.GetType() == GraphicType::Bitmap)
+ {
+ const BitmapEx& rGraphicBitmap = aGraphic.GetBitmapExRef();
+ if (rGraphicBitmap.GetChecksum() == rBmpEx.GetChecksum())
+ {
+ bool bPNG = false;
+ GfxLink aGfxLink = aGraphic.GetGfxLink();
+ if (aGfxLink.GetType() == GfxLinkType::NativePng)
+ {
+ bPNG = true;
+ }
+ else if (aGfxLink.GetType() == GfxLinkType::NativeJpg)
+ {
+ bJPG = true;
+ }
+ if (bPNG || bJPG)
+ {
+ aOStm.WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize());
+ bCached = true;
+ }
+ }
+ }
+ }
+
+ const BitmapEx* pBitmap = &rBmpEx;
+ std::unique_ptr<BitmapEx> pNewBitmap;
+
+ // for preview we generate downscaled images (1280x720 max)
+ if (mbIsPreview)
+ {
+ Size aSize = rBmpEx.GetSizePixel();
+ double fX = static_cast<double>(aSize.getWidth()) / 1280;
+ double fY = static_cast<double>(aSize.getHeight()) / 720;
+ double fFactor = fX > fY ? fX : fY;
+ if (fFactor > 1.0)
+ {
+ aSize.setWidth(aSize.getWidth() / fFactor);
+ aSize.setHeight(aSize.getHeight() / fFactor);
+ pNewBitmap = std::make_unique<BitmapEx>(rBmpEx);
+ pNewBitmap->Scale(aSize);
+ pBitmap = pNewBitmap.get();
+ }
+ }
+
+ if( !(bCached || GraphicConverter::Export( aOStm, *pBitmap, ConvertDataFormat::PNG ) == ERRCODE_NONE) )
+ return;
+
+ Point aPt;
+ Size aSz;
+ Sequence< sal_Int8 > aSeq( static_cast<sal_Int8 const *>(aOStm.GetData()), aOStm.Tell() );
+ OUStringBuffer aBuffer;
+ if (bJPG)
+ {
+ aBuffer.append("data:image/jpeg;base64,");
+ }
+ else
+ {
+ aBuffer.append("data:image/png;base64,");
+ }
+ ::comphelper::Base64::encode( aBuffer, aSeq );
+
+ ImplMap( rPt, aPt );
+ ImplMap( rSz, aSz );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrX, OUString::number( aPt.X() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrY, OUString::number( aPt.Y() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number( aSz.Width() ) );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number( aSz.Height() ) );
+
+ // the image must be scaled to aSz in a non-uniform way
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, "preserveAspectRatio", "none" );
+
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, aBuffer.makeStringAndClear() );
+ {
+ SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, "image", true, true );
+ }
+}
+
+
+void SVGActionWriter::ImplWriteActions( const GDIMetaFile& rMtf,
+ sal_uInt32 nWriteFlags,
+ const OUString& aElementId,
+ const Reference< css::drawing::XShape >* pxShape,
+ const GDIMetaFile* pTextEmbeddedBitmapMtf )
+{
+ // need a counter for the actions written per shape to avoid double ID
+ // generation
+ sal_Int32 nEntryCount(0);
+
+ bool bUseElementId = !aElementId.isEmpty();
+
+#if OSL_DEBUG_LEVEL > 0
+ bool bIsTextShape = false;
+ if( !mrExport.IsUsePositionedCharacters() && pxShape
+ && Reference< XText >( *pxShape, UNO_QUERY ).is() )
+ {
+ bIsTextShape = true;
+ }
+#endif
+ mbIsPlaceholderShape = false;
+ if( bUseElementId && ( aElementId == sPlaceholderTag ) )
+ {
+ mbIsPlaceholderShape = true;
+ // since we utilize aElementId in an improper way we reset the boolean
+ // control variable bUseElementId to false before to go on
+ bUseElementId = false;
+ }
+
+ for( size_t nCurAction = 0, nCount = rMtf.GetActionSize(); nCurAction < nCount; nCurAction++ )
+ {
+ const MetaAction* pAction = rMtf.GetAction( nCurAction );
+ const MetaActionType nType = pAction->GetType();
+
+#if OSL_DEBUG_LEVEL > 0
+ if( bIsTextShape )
+ {
+ try
+ {
+ SvXMLElementExport aElem( mrExport,
+ XML_NAMESPACE_NONE, "desc", false, false );
+ OUStringBuffer sType(OUString::number(static_cast<sal_uInt16>(nType)));
+ if (pAction && (nType == MetaActionType::COMMENT))
+ {
+ sType.append(": ");
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ OString sComment = pA->GetComment();
+ if (!sComment.isEmpty())
+ {
+ sType.append(OStringToOUString(
+ sComment, RTL_TEXTENCODING_UTF8));
+ }
+ if (sComment.equalsIgnoreAsciiCase("FIELD_SEQ_BEGIN"))
+ {
+ sal_uInt8 const*const pData = pA->GetData();
+ if (pData && (pA->GetDataSize()))
+ {
+ sal_uInt16 sz = static_cast<sal_uInt16>((pA->GetDataSize()) / 2);
+ if (sz)
+ {
+ sType.append("; ");
+ sType.append(
+ reinterpret_cast<sal_Unicode const*>(pData),
+ sz);
+ }
+ }
+ }
+ }
+ if (sType.getLength())
+ {
+ mrExport.GetDocHandler()->characters(
+ sType.makeStringAndClear());
+ }
+ }
+ catch( ... )
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+ SAL_WARN( "filter.svg", pA->GetComment() );
+ }
+
+ }
+#endif
+ switch( nType )
+ {
+ case MetaActionType::PIXEL:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPixelAction* pA = static_cast<const MetaPixelAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( pA->GetColor(), pA->GetColor() );
+ ImplWriteLine( pA->GetPoint(), pA->GetPoint(), &pA->GetColor() );
+ }
+ }
+ break;
+
+ case MetaActionType::POINT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPointAction* pA = static_cast<const MetaPointAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetLineColor() );
+ ImplWriteLine( pA->GetPoint(), pA->GetPoint() );
+ }
+ }
+ break;
+
+ case MetaActionType::LINE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaLineAction* pA = static_cast<const MetaLineAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetLineColor() );
+ ImplWriteLine( pA->GetStartPoint(), pA->GetEndPoint() );
+ }
+ }
+ break;
+
+ case MetaActionType::RECT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWriteRect( static_cast<const MetaRectAction*>(pAction)->GetRect() );
+ }
+ }
+ break;
+
+ case MetaActionType::ROUNDRECT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaRoundRectAction* pA = static_cast<const MetaRoundRectAction*>(pAction);
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWriteRect( pA->GetRect(), pA->GetHorzRound(), pA->GetVertRound() );
+ }
+ }
+ break;
+
+ case MetaActionType::ELLIPSE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaEllipseAction* pA = static_cast<const MetaEllipseAction*>(pAction);
+ const tools::Rectangle& rRect = pA->GetRect();
+
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWriteEllipse( rRect.Center(), rRect.GetWidth() >> 1, rRect.GetHeight() >> 1 );
+ }
+ }
+ break;
+
+ case MetaActionType::ARC:
+ case MetaActionType::PIE:
+ case MetaActionType::CHORD:
+ case MetaActionType::POLYGON:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ tools::Polygon aPoly;
+
+ switch( nType )
+ {
+ case MetaActionType::ARC:
+ {
+ const MetaArcAction* pA = static_cast<const MetaArcAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Arc );
+ }
+ break;
+
+ case MetaActionType::PIE:
+ {
+ const MetaPieAction* pA = static_cast<const MetaPieAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Pie );
+ }
+ break;
+
+ case MetaActionType::CHORD:
+ {
+ const MetaChordAction* pA = static_cast<const MetaChordAction*>(pAction);
+ aPoly = tools::Polygon( pA->GetRect(), pA->GetStartPoint(), pA->GetEndPoint(), PolyStyle::Chord );
+ }
+ break;
+
+ case MetaActionType::POLYGON:
+ aPoly = static_cast<const MetaPolygonAction*>(pAction)->GetPolygon();
+ break;
+ default: break;
+ }
+
+ if( aPoly.GetSize() )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWritePolyPolygon( aPoly, false );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::POLYLINE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPolyLineAction* pA = static_cast<const MetaPolyLineAction*>(pAction);
+ const tools::Polygon& rPoly = pA->GetPolygon();
+
+ if( rPoly.GetSize() )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), COL_TRANSPARENT );
+ ImplAddLineAttr( pA->GetLineInfo() );
+ ImplWritePolyPolygon( rPoly, true );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::POLYPOLYGON:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaPolyPolygonAction* pA = static_cast<const MetaPolyPolygonAction*>(pAction);
+ const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
+
+ if( rPolyPoly.Count() )
+ {
+ maAttributeWriter.AddPaintAttr( mpVDev->GetLineColor(), mpVDev->GetFillColor() );
+ ImplWritePolyPolygon( rPolyPoly, false );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::GRADIENT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaGradientAction* pA = static_cast<const MetaGradientAction*>(pAction);
+ const tools::Polygon aRectPoly( pA->GetRect() );
+ const tools::PolyPolygon aRectPolyPoly( aRectPoly );
+
+ ImplWriteGradientEx( aRectPolyPoly, pA->GetGradient(), nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::GRADIENTEX:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction);
+ ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::HATCH:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaHatchAction* pA = static_cast<const MetaHatchAction*>(pAction);
+ ImplWritePattern( pA->GetPolyPolygon(), &pA->GetHatch(), nullptr, nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::Transparent:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaTransparentAction* pA = static_cast<const MetaTransparentAction*>(pAction);
+ const tools::PolyPolygon& rPolyPoly = pA->GetPolyPolygon();
+
+ if( rPolyPoly.Count() )
+ {
+ Color aNewLineColor( mpVDev->GetLineColor() ), aNewFillColor( mpVDev->GetFillColor() );
+
+ // tdf#149800 do not change transparency of fully transparent
+ // i.e. invisible line, because it makes it visible,
+ // resulting an extra line behind the normal shape line
+ if ( aNewLineColor.GetAlpha() > 0 )
+ aNewLineColor.SetAlpha( 255 - sal::static_int_cast<sal_uInt8>( FRound( pA->GetTransparence() * 2.55 ) ) );
+ aNewFillColor.SetAlpha( 255 - sal::static_int_cast<sal_uInt8>( FRound( pA->GetTransparence() * 2.55 ) ) );
+
+ maAttributeWriter.AddPaintAttr( aNewLineColor, aNewFillColor );
+ ImplWritePolyPolygon( rPolyPoly, false );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::FLOATTRANSPARENT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaFloatTransparentAction* pA = static_cast<const MetaFloatTransparentAction*>(pAction);
+ GDIMetaFile aTmpMtf( pA->GetGDIMetaFile() );
+ ImplWriteMask( aTmpMtf, pA->GetPoint(), pA->GetSize(),
+ pA->GetGradient(), nWriteFlags );
+ }
+ }
+ break;
+
+ case MetaActionType::EPS:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaEPSAction* pA = static_cast<const MetaEPSAction*>(pAction);
+ const GDIMetaFile& aGDIMetaFile( pA->GetSubstitute() );
+ bool bFound = false;
+
+ for( sal_uInt32 k = 0, nCount2 = aGDIMetaFile.GetActionSize(); ( k < nCount2 ) && !bFound; ++k )
+ {
+ const MetaAction* pSubstAct = aGDIMetaFile.GetAction( k );
+
+ if( pSubstAct->GetType() == MetaActionType::BMPSCALE )
+ {
+ bFound = true;
+ const MetaBmpScaleAction* pBmpScaleAction = static_cast<const MetaBmpScaleAction*>(pSubstAct);
+ ImplWriteBmp( BitmapEx(pBmpScaleAction->GetBitmap()),
+ pA->GetPoint(), pA->GetSize(),
+ Point(), pBmpScaleAction->GetBitmap().GetSizePixel(), pxShape );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::COMMENT:
+ {
+ const MetaCommentAction* pA = static_cast<const MetaCommentAction*>(pAction);
+
+ if( ( pA->GetComment().equalsIgnoreAsciiCase("XGRAD_SEQ_BEGIN") ) &&
+ ( nWriteFlags & SVGWRITER_WRITE_FILL ) )
+ {
+ const MetaGradientExAction* pGradAction = nullptr;
+ bool bDone = false;
+
+ while( !bDone && ( ++nCurAction < nCount ) )
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ pGradAction = static_cast<const MetaGradientExAction*>(pAction);
+ else if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>( pAction )->GetComment().
+ equalsIgnoreAsciiCase("XGRAD_SEQ_END") ) )
+ {
+ bDone = true;
+ }
+ }
+
+ if( pGradAction )
+ ImplWriteGradientEx( pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags );
+ }
+ else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHFILL_SEQ_BEGIN") ) &&
+ ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) &&
+ pA->GetDataSize() )
+ {
+ // write open shape in every case
+ if (mapCurShape)
+ {
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ );
+ SvtGraphicFill aFill;
+
+ ReadSvtGraphicFill( aMemStm, aFill );
+
+ bool bGradient = SvtGraphicFill::fillGradient == aFill.getFillType() &&
+ ( SvtGraphicFill::GradientType::Linear == aFill.getGradientType() ||
+ SvtGraphicFill::GradientType::Radial == aFill.getGradientType() );
+ bool bSkip = ( SvtGraphicFill::fillSolid == aFill.getFillType() || bGradient );
+
+ if( bSkip )
+ {
+ tools::PolyPolygon aShapePolyPoly;
+
+ aFill.getPath( aShapePolyPoly );
+
+ if( aShapePolyPoly.Count() )
+ {
+ mapCurShape.reset( new SVGShapeDescriptor );
+
+ if( bUseElementId )
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ mapCurShape->maShapePolyPoly = aShapePolyPoly;
+ mapCurShape->maShapeFillColor = aFill.getFillColor();
+ mapCurShape->maShapeFillColor.SetAlpha( 255 - static_cast<sal_uInt8>(FRound( 255.0 * aFill.getTransparency() )) );
+
+ if( bGradient )
+ {
+ // step through following actions until the first Gradient/GradientEx action is found
+ while (!mapCurShape->mapShapeGradient && bSkip
+ && (++nCurAction < nCount))
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().
+ equalsIgnoreAsciiCase("XPATHFILL_SEQ_END") ) )
+ {
+ bSkip = false;
+ }
+ else if( pAction->GetType() == MetaActionType::GRADIENTEX )
+ {
+ mapCurShape->mapShapeGradient.reset( new Gradient(
+ static_cast< const MetaGradientExAction* >( pAction )->GetGradient() ) );
+ }
+ else if( pAction->GetType() == MetaActionType::GRADIENT )
+ {
+ mapCurShape->mapShapeGradient.reset( new Gradient(
+ static_cast< const MetaGradientAction* >( pAction )->GetGradient() ) );
+ }
+ }
+ }
+ }
+ else
+ bSkip = false;
+ }
+
+ // skip rest of comment
+ while( bSkip && ( ++nCurAction < nCount ) )
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>( pAction )->GetComment().
+ equalsIgnoreAsciiCase("XPATHFILL_SEQ_END") ) )
+ {
+ bSkip = false;
+ }
+ }
+ }
+ else if( ( pA->GetComment().equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_BEGIN") ) &&
+ ( nWriteFlags & SVGWRITER_WRITE_FILL ) && !( nWriteFlags & SVGWRITER_NO_SHAPE_COMMENTS ) &&
+ pA->GetDataSize() )
+ {
+ SvMemoryStream aMemStm( const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ );
+ SvtGraphicStroke aStroke;
+ tools::PolyPolygon aStartArrow, aEndArrow;
+
+ ReadSvtGraphicStroke( aMemStm, aStroke );
+ aStroke.getStartArrow( aStartArrow );
+ aStroke.getEndArrow( aEndArrow );
+
+ // Currently no support for strokes with start/end arrow(s)
+ // added that support
+ tools::Polygon aPoly;
+
+ aStroke.getPath(aPoly);
+
+ if (mapCurShape)
+ {
+ if(1 != mapCurShape->maShapePolyPoly.Count()
+ || !mapCurShape->maShapePolyPoly[0].IsEqual(aPoly))
+ {
+ // this path action is not covering the same path than the already existing
+ // fill polypolygon, so write out the fill polygon
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+ }
+
+ if (!mapCurShape)
+ {
+
+ mapCurShape.reset( new SVGShapeDescriptor );
+
+ if( bUseElementId )
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ mapCurShape->maShapePolyPoly = aPoly;
+ }
+
+ mapCurShape->maShapeLineColor = mpVDev->GetLineColor();
+ mapCurShape->maShapeLineColor.SetAlpha( 255 - static_cast<sal_uInt8>(FRound( aStroke.getTransparency() * 255.0 )) );
+ mapCurShape->mnStrokeWidth = FRound( aStroke.getStrokeWidth() );
+ aStroke.getDashArray( mapCurShape->maDashArray );
+
+ // added support for LineJoin
+ switch(aStroke.getJoinType())
+ {
+ default: /* SvtGraphicStroke::joinMiter, SvtGraphicStroke::joinNone */
+ {
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Miter;
+ break;
+ }
+ case SvtGraphicStroke::joinRound:
+ {
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Round;
+ break;
+ }
+ case SvtGraphicStroke::joinBevel:
+ {
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Bevel;
+ break;
+ }
+ }
+
+ // added support for LineCap
+ switch(aStroke.getCapType())
+ {
+ default: /* SvtGraphicStroke::capButt */
+ {
+ mapCurShape->maLineCap = css::drawing::LineCap_BUTT;
+ break;
+ }
+ case SvtGraphicStroke::capRound:
+ {
+ mapCurShape->maLineCap = css::drawing::LineCap_ROUND;
+ break;
+ }
+ case SvtGraphicStroke::capSquare:
+ {
+ mapCurShape->maLineCap = css::drawing::LineCap_SQUARE;
+ break;
+ }
+ }
+
+ if (mapCurShape->maShapePolyPoly.Count() && (aStartArrow.Count() || aEndArrow.Count()))
+ {
+ ImplWriteShape( *mapCurShape );
+
+ mapCurShape->maShapeFillColor = mapCurShape->maShapeLineColor;
+ mapCurShape->maShapeLineColor = COL_TRANSPARENT;
+ mapCurShape->mnStrokeWidth = 0;
+ mapCurShape->maDashArray.clear();
+ mapCurShape->maLineJoin = basegfx::B2DLineJoin::Miter;
+ mapCurShape->maLineCap = css::drawing::LineCap_BUTT;
+
+ if(aStartArrow.Count())
+ {
+ mapCurShape->maShapePolyPoly = aStartArrow;
+
+ if( bUseElementId ) // #i124825# aElementId is optional, may be zero
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ ImplWriteShape( *mapCurShape );
+ }
+
+ if(aEndArrow.Count())
+ {
+ mapCurShape->maShapePolyPoly = aEndArrow;
+
+ if( bUseElementId ) // #i124825# aElementId is optional, may be zero
+ {
+ mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
+ }
+
+ ImplWriteShape( *mapCurShape );
+ }
+
+ mapCurShape.reset();
+ }
+
+ // write open shape in every case
+ if (mapCurShape)
+ {
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+
+ // skip rest of comment
+ bool bSkip = true;
+
+ while( bSkip && ( ++nCurAction < nCount ) )
+ {
+ pAction = rMtf.GetAction( nCurAction );
+
+ if( ( pAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pAction)->GetComment().
+ equalsIgnoreAsciiCase("XPATHSTROKE_SEQ_END") ) )
+ {
+ bSkip = false;
+ }
+ }
+ }
+ else if( !mrExport.IsUsePositionedCharacters() && ( nWriteFlags & SVGWRITER_WRITE_TEXT ) )
+ {
+ if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_BEGIN" ) )
+ {
+ if( pxShape )
+ {
+ Reference< XText > xText( *pxShape, UNO_QUERY );
+ if( xText.is() )
+ maTextWriter.setTextShape( xText, pTextEmbeddedBitmapMtf );
+ }
+ maTextWriter.createParagraphEnumeration();
+ {
+ // nTextFound == -1 => no text found
+ // nTextFound == 0 => no text found and end of text shape reached
+ // nTextFound == 1 => text found!
+ sal_Int32 nTextFound = -1;
+ while( ( nTextFound < 0 ) && ( nCurAction < nCount ) )
+ {
+ nTextFound
+ = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
+ }
+ // We found some text in the current text shape.
+ if( nTextFound > 0 )
+ {
+ maTextWriter.setTextProperties( rMtf, nCurAction );
+ maTextWriter.startTextShape();
+ }
+ // We reached the end of the current text shape
+ // without finding any text. So we need to go back
+ // by one action in order to handle the
+ // XTEXT_PAINTSHAPE_END action because on the next
+ // loop the nCurAction is incremented by one.
+ else
+ {
+ --nCurAction;
+ }
+ }
+ }
+ else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_PAINTSHAPE_END" ) )
+ {
+ maTextWriter.endTextShape();
+ }
+ else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_EOP" ) )
+ {
+ const MetaAction* pNextAction = rMtf.GetAction( nCurAction + 1 );
+ if( !( ( pNextAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pNextAction)->GetComment().equalsIgnoreAsciiCase("XTEXT_PAINTSHAPE_END") ) ))
+ {
+ // nTextFound == -1 => no text found and end of paragraph reached
+ // nTextFound == 0 => no text found and end of text shape reached
+ // nTextFound == 1 => text found!
+ sal_Int32 nTextFound = -1;
+ while( ( nTextFound < 0 ) && ( nCurAction < nCount ) )
+ {
+ nTextFound
+ = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
+ }
+ // We found a paragraph with some text in the
+ // current text shape.
+ if( nTextFound > 0 )
+ {
+ maTextWriter.setTextProperties( rMtf, nCurAction );
+ maTextWriter.startTextParagraph();
+ }
+ // We reached the end of the current text shape
+ // without finding any text. So we need to go back
+ // by one action in order to handle the
+ // XTEXT_PAINTSHAPE_END action because on the next
+ // loop the nCurAction is incremented by one.
+ else
+ {
+ --nCurAction;
+ }
+
+ }
+ }
+ else if( pA->GetComment().equalsIgnoreAsciiCase( "XTEXT_EOL" ) )
+ {
+ const MetaAction* pNextAction = rMtf.GetAction( nCurAction + 1 );
+ if( !( ( pNextAction->GetType() == MetaActionType::COMMENT ) &&
+ ( static_cast<const MetaCommentAction*>(pNextAction)->GetComment().equalsIgnoreAsciiCase("XTEXT_EOP") ) ) )
+ {
+ // nTextFound == -2 => no text found and end of line reached
+ // nTextFound == -1 => no text found and end of paragraph reached
+ // nTextFound == 1 => text found!
+ sal_Int32 nTextFound = -2;
+ while( ( nTextFound < -1 ) && ( nCurAction < nCount ) )
+ {
+ nTextFound
+ = maTextWriter.setTextPosition(rMtf, nCurAction, nWriteFlags);
+ }
+ // We found a line with some text in the current
+ // paragraph.
+ if( nTextFound > 0 )
+ {
+ maTextWriter.startTextPosition();
+ }
+ // We reached the end of the current paragraph
+ // without finding any text. So we need to go back
+ // by one action in order to handle the XTEXT_EOP
+ // action because on the next loop the nCurAction is
+ // incremented by one.
+ else
+ {
+ --nCurAction;
+ }
+ }
+ }
+ }
+ else if( pA->GetComment().startsWithIgnoreAsciiCase( sTiledBackgroundTag ) )
+ {
+ // In the tile case the background is rendered through a rectangle
+ // filled by exploiting an exported pattern element.
+ // Both the pattern and the rectangle are embedded in a <defs> element.
+ // The comment content has the following format: "SLIDE_BACKGROUND <background-id>"
+ const OString& sComment = pA->GetComment();
+ OUString sRefId = "#" + OUString::fromUtf8( o3tl::getToken(sComment, 1, ' ') );
+ mrExport.AddAttribute( XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, sRefId );
+
+ SvXMLElementExport aRefElem( mrExport, XML_NAMESPACE_NONE, "use", true, true );
+ }
+ }
+ break;
+
+ case MetaActionType::BMP:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpAction* pA = static_cast<const MetaBmpAction*>(pAction);
+
+ ImplWriteBmp( BitmapEx(pA->GetBitmap()),
+ pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmap().GetSizePixel() ),
+ Point(), pA->GetBitmap().GetSizePixel(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::BMPSCALE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpScaleAction* pA = static_cast<const MetaBmpScaleAction*>(pAction);
+
+ // Bitmaps embedded into text shapes are collected and exported elsewhere.
+ if( maTextWriter.isTextShapeStarted() )
+ {
+ maTextWriter.writeBitmapPlaceholder( pA );
+ }
+ else
+ {
+ ImplWriteBmp( BitmapEx(pA->GetBitmap()),
+ pA->GetPoint(), pA->GetSize(),
+ Point(), pA->GetBitmap().GetSizePixel(), pxShape );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMPSCALEPART:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpScalePartAction* pA = static_cast<const MetaBmpScalePartAction*>(pAction);
+
+ ImplWriteBmp( BitmapEx(pA->GetBitmap()),
+ pA->GetDestPoint(), pA->GetDestSize(),
+ pA->GetSrcPoint(), pA->GetSrcSize(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::BMPEX:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpExAction* pA = static_cast<const MetaBmpExAction*>(pAction);
+
+ ImplWriteBmp( pA->GetBitmapEx(),
+ pA->GetPoint(), mpVDev->PixelToLogic( pA->GetBitmapEx().GetSizePixel() ),
+ Point(), pA->GetBitmapEx().GetSizePixel(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALE:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpExScaleAction* pA = static_cast<const MetaBmpExScaleAction*>(pAction);
+
+ // Bitmaps embedded into text shapes are collected and exported elsewhere.
+ if( maTextWriter.isTextShapeStarted() )
+ {
+ maTextWriter.writeBitmapPlaceholder( pA );
+ }
+ else
+ {
+ ImplWriteBmp( pA->GetBitmapEx(),
+ pA->GetPoint(), pA->GetSize(),
+ Point(), pA->GetBitmapEx().GetSizePixel(), pxShape );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::BMPEXSCALEPART:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_FILL )
+ {
+ const MetaBmpExScalePartAction* pA = static_cast<const MetaBmpExScalePartAction*>(pAction);
+
+ ImplWriteBmp( pA->GetBitmapEx(),
+ pA->GetDestPoint(), pA->GetDestSize(),
+ pA->GetSrcPoint(), pA->GetSrcSize(), pxShape );
+ }
+ }
+ break;
+
+ case MetaActionType::TEXT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaTextAction* pA = static_cast<const MetaTextAction*>(pAction);
+ sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() );
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength );
+
+ if( !aText.isEmpty() )
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetPoint(), aText, {}, 0 );
+ }
+ else
+ {
+ maTextWriter.writeTextPortion( pA->GetPoint(), aText );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTRECT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaTextRectAction* pA = static_cast<const MetaTextRectAction*>(pAction);
+
+ if (!pA->GetText().isEmpty())
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetRect().TopLeft(), pA->GetText(), {}, 0 );
+ }
+ maTextWriter.writeTextPortion( pA->GetRect().TopLeft(), pA->GetText() );
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::TEXTARRAY:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaTextArrayAction* pA = static_cast<const MetaTextArrayAction*>(pAction);
+ sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() );
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength );
+
+ if( !aText.isEmpty() )
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetPoint(), aText, pA->GetDXArray(), 0 );
+ }
+ else
+ {
+ maTextWriter.writeTextPortion( pA->GetPoint(), aText );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::STRETCHTEXT:
+ {
+ if( nWriteFlags & SVGWRITER_WRITE_TEXT )
+ {
+ const MetaStretchTextAction* pA = static_cast<const MetaStretchTextAction*>(pAction);
+ sal_Int32 aLength = std::min( pA->GetText().getLength(), pA->GetLen() );
+ const OUString aText = pA->GetText().copy( pA->GetIndex(), aLength );
+
+ if( !aText.isEmpty() )
+ {
+ if( mrExport.IsUsePositionedCharacters() )
+ {
+ vcl::Font aFont = ImplSetCorrectFontHeight();
+ maAttributeWriter.SetFontAttr( aFont );
+ ImplWriteText( pA->GetPoint(), aText, {}, pA->GetWidth() );
+ }
+ else
+ {
+ maTextWriter.writeTextPortion( pA->GetPoint(), aText );
+ }
+ }
+ }
+ }
+ break;
+
+ case MetaActionType::CLIPREGION:
+ case MetaActionType::ISECTRECTCLIPREGION:
+ case MetaActionType::ISECTREGIONCLIPREGION:
+ case MetaActionType::MOVECLIPREGION:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+ const vcl::Region& rClipRegion = mpVDev->GetActiveClipRegion();
+ ImplWriteClipPath( rClipRegion.GetAsPolyPolygon() );
+
+ mbClipAttrChanged = true;
+ }
+ break;
+
+ case MetaActionType::PUSH:
+ {
+ const MetaPushAction* pA = static_cast<const MetaPushAction*>(pAction);
+ vcl::PushFlags mnFlags = pA->GetFlags();
+
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+
+ maContextHandler.pushState( mnFlags );
+ }
+ break;
+
+ case MetaActionType::POP:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+
+ vcl::PushFlags mnFlags = maContextHandler.getPushFlags();
+
+ maContextHandler.popState();
+
+ if( mnFlags & vcl::PushFlags::CLIPREGION )
+ {
+ ImplEndClipRegion();
+ ImplStartClipRegion( mrCurrentState.nRegionClipPathId );
+ }
+ }
+ break;
+
+ case MetaActionType::REFPOINT:
+ case MetaActionType::MAPMODE:
+ case MetaActionType::LINECOLOR:
+ case MetaActionType::FILLCOLOR:
+ case MetaActionType::TEXTLINECOLOR:
+ case MetaActionType::TEXTFILLCOLOR:
+ case MetaActionType::TEXTCOLOR:
+ case MetaActionType::TEXTALIGN:
+ case MetaActionType::FONT:
+ case MetaActionType::LAYOUTMODE:
+ {
+ const_cast<MetaAction*>(pAction)->Execute( mpVDev );
+ }
+ break;
+
+ case MetaActionType::RASTEROP:
+ case MetaActionType::MASK:
+ case MetaActionType::MASKSCALE:
+ case MetaActionType::MASKSCALEPART:
+ case MetaActionType::WALLPAPER:
+ case MetaActionType::TEXTLINE:
+ case MetaActionType::TEXTLANGUAGE:
+ {
+ // !!! >>> we don't want to support these actions
+ }
+ break;
+
+ default:
+ SAL_WARN("filter.svg", "SVGActionWriter::ImplWriteActions: unsupported MetaAction # " << sal_Int32(nType));
+ break;
+ }
+ }
+}
+
+
+vcl::Font SVGActionWriter::ImplSetCorrectFontHeight() const
+{
+ vcl::Font aFont( mpVDev->GetFont() );
+ Size aSz;
+
+ ImplMap( Size( 0, aFont.GetFontHeight() ), aSz );
+
+ aFont.SetFontHeight( aSz.Height() );
+
+ return aFont;
+}
+
+
+void SVGActionWriter::WriteMetaFile( const Point& rPos100thmm,
+ const Size& rSize100thmm,
+ const GDIMetaFile& rMtf,
+ sal_uInt32 nWriteFlags,
+ const OUString& aElementId,
+ const Reference< css::drawing::XShape >* pXShape,
+ const GDIMetaFile* pTextEmbeddedBitmapMtf )
+{
+ MapMode aMapMode( rMtf.GetPrefMapMode() );
+ Size aPrefSize( rMtf.GetPrefSize() );
+ Fraction aFractionX( aMapMode.GetScaleX() );
+ Fraction aFractionY( aMapMode.GetScaleY() );
+
+ mpVDev->Push();
+
+ Size aSize( OutputDevice::LogicToLogic(rSize100thmm, MapMode(MapUnit::Map100thMM), aMapMode) );
+ aFractionX *= Fraction( aSize.Width(), aPrefSize.Width() );
+ aMapMode.SetScaleX( aFractionX );
+ aFractionY *= Fraction( aSize.Height(), aPrefSize.Height() );
+ aMapMode.SetScaleY( aFractionY );
+
+ Point aOffset( OutputDevice::LogicToLogic(rPos100thmm, MapMode(MapUnit::Map100thMM), aMapMode ) );
+ aOffset += aMapMode.GetOrigin();
+ aMapMode.SetOrigin( aOffset );
+
+ mpVDev->SetMapMode( aMapMode );
+
+ mapCurShape.reset();
+
+ ImplWriteActions( rMtf, nWriteFlags, aElementId, pXShape, pTextEmbeddedBitmapMtf );
+ maTextWriter.endTextParagraph();
+ ImplEndClipRegion();
+
+ // draw open shape that doesn't have a border
+ if (mapCurShape)
+ {
+ ImplWriteShape( *mapCurShape );
+ mapCurShape.reset();
+ }
+
+ mpVDev->Pop();
+}
+
+
+SVGWriter::SVGWriter( const Sequence<Any>& args, const Reference< XComponentContext >& rxCtx )
+ : mxContext(rxCtx)
+{
+ if(args.getLength()==1)
+ args[0]>>=maFilterData;
+}
+
+
+SVGWriter::~SVGWriter()
+{
+}
+
+
+void SAL_CALL SVGWriter::write( const Reference<XDocumentHandler>& rxDocHandler,
+ const Sequence<sal_Int8>& rMtfSeq )
+{
+ SvMemoryStream aMemStm( const_cast<sal_Int8 *>(rMtfSeq.getConstArray()), rMtfSeq.getLength(), StreamMode::READ );
+ GDIMetaFile aMtf;
+
+ SvmReader aReader( aMemStm );
+ aReader.Read( aMtf );
+
+ rtl::Reference<SVGExport> pWriter(new SVGExport( mxContext, rxDocHandler, maFilterData ));
+ pWriter->writeMtf( aMtf );
+}
+
+// XServiceInfo
+sal_Bool SVGWriter::supportsService(const OUString& sServiceName)
+{
+ return cppu::supportsService(this, sServiceName);
+}
+OUString SVGWriter::getImplementationName()
+{
+ return "com.sun.star.comp.Draw.SVGWriter";
+}
+css::uno::Sequence< OUString > SVGWriter::getSupportedServiceNames()
+{
+ return { "com.sun.star.svg.SVGWriter" };
+}
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+filter_SVGWriter_get_implementation(
+ css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args)
+{
+ return cppu::acquire(new SVGWriter(args, context));
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */