/* -*- 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 <svx/svdomedia.hxx>
#include <basegfx/utils/bgradient.hxx>
#include <tools/vcompat.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>
 
using namespace ::com::sun::star::style;
 
constexpr OUString aPrefixClipPathId = u"clip_path_"_ustr;
 
constexpr OUString aXMLElemG = u"g"_ustr;
constexpr OUString aXMLElemDefs = u"defs"_ustr;
constexpr OUString aXMLElemText = u"text"_ustr;
constexpr OUString aXMLElemTspan = u"tspan"_ustr;
constexpr OUString aXMLElemLinearGradient = u"linearGradient"_ustr;
constexpr OUString aXMLElemStop = u"stop"_ustr;
 
constexpr OUString aXMLAttrTransform = u"transform"_ustr;
constexpr OUString aXMLAttrStyle = u"style"_ustr;
constexpr OUString aXMLAttrId = u"id"_ustr;
constexpr OUString aXMLAttrX = u"x"_ustr;
constexpr OUString aXMLAttrY = u"y"_ustr;
constexpr OUString aXMLAttrX1 = u"x1"_ustr;
constexpr OUString aXMLAttrY1 = u"y1"_ustr;
constexpr OUString aXMLAttrX2 = u"x2"_ustr;
constexpr OUString aXMLAttrY2 = u"y2"_ustr;
constexpr OUString aXMLAttrCX = u"cx"_ustr;
constexpr OUString aXMLAttrCY = u"cy"_ustr;
constexpr OUString aXMLAttrRX = u"rx"_ustr;
constexpr OUString aXMLAttrRY = u"ry"_ustr;
constexpr OUString aXMLAttrWidth = u"width"_ustr;
constexpr OUString aXMLAttrHeight = u"height"_ustr;
constexpr OUString aXMLAttrStrokeWidth = u"stroke-width"_ustr;
constexpr OUString aXMLAttrFill = u"fill"_ustr;
constexpr OUString aXMLAttrFontFamily = u"font-family"_ustr;
constexpr OUString aXMLAttrFontSize = u"font-size"_ustr;
constexpr OUString aXMLAttrFontStyle = u"font-style"_ustr;
constexpr OUString aXMLAttrFontWeight = u"font-weight"_ustr;
constexpr OUString aXMLAttrTextDecoration = u"text-decoration"_ustr;
constexpr OUString aXMLAttrXLinkHRef = u"xlink:href"_ustr;
constexpr OUString aXMLAttrGradientUnits = u"gradientUnits"_ustr;
constexpr OUString aXMLAttrOffset = u"offset"_ustr;
constexpr OUString aXMLAttrStopColor = u"stop-color"_ustr;
constexpr OUString aXMLAttrStrokeLinejoin = u"stroke-linejoin"_ustr;
constexpr OUString aXMLAttrStrokeLinecap = u"stroke-linecap"_ustr;
 
 
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, u"fill-opacity"_ustr, rFillColor );
 
    // Stroke
    AddColorAttr( u"stroke"_ustr, u"stroke-opacity"_ustr, rLineColor );
}
 
 
void SVGAttributeWriter::AddGradientDef( const tools::Rectangle& rObjRect, const Gradient& rGradient, OUString& rGradientId )
{
    if( rObjRect.GetWidth() && rObjRect.GetHeight() &&
        ( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::GradientStyle_AXIAL ||
          rGradient.GetStyle() == css::awt::GradientStyle_RADIAL || rGradient.GetStyle() == css::awt::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() == css::awt::GradientStyle_LINEAR || rGradient.GetStyle() == css::awt::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, u"userSpaceOnUse"_ustr );
                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() == css::awt::GradientStyle_AXIAL ) ? 0.005 : 0.01 );
 
                ImplGetColorStr( ( rGradient.GetStyle() == css::awt::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() == css::awt::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() != css::awt::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, u"userSpaceOnUse"_ustr );
                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, u"r"_ustr, OUString::number( ImplRound( fRadius ) ) );
 
                apGradient.reset( new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, u"radialGradient"_ustr, 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;
 
    // similar to OutputDevice::Push, but we may conditionally not restore these
    MapMode aOrigMapMode = mpVDev->GetMapMode();
    bool bOrigMapMapModeEnabled = mpVDev->IsMapModeEnabled();
    int nPopsNeeded = 0;
 
    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, pA->getSVGTransparencyColorStops(), &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;
 
            case MetaActionType::PUSH:
                const_cast<MetaAction*>(pAction)->Execute(mpVDev);
                ++nPopsNeeded;
                break;
            case MetaActionType::POP:
                const_cast<MetaAction*>(pAction)->Execute(mpVDev);
                --nPopsNeeded;
                break;
            case MetaActionType::MAPMODE:
            {
                // keep MapMode up to date
                const_cast<MetaAction*>(pAction)->Execute(mpVDev);
                break;
            }
            break;
 
            default: break;
        }
        if( bConfigured || bEOL || bEOP || bETS ) break;
    }
    implMap( aPos, maTextPos );
 
    if( bEmpty )
    {
        // If we fast-forward to this nActionIndex, then leave
        // the OutputDevice state as it is.
        nCurAction = nActionIndex;
        return ( bEOL ? -2 : ( bEOP ? -1 : 0 ) );
    }
 
    // If we are leaving nCurAction untouched, then restore the OutputDevice
    // to its original state
    while (nPopsNeeded > 0)
    {
        mpVDev->Pop();
        --nPopsNeeded;
    }
 
    mpVDev->SetMapMode(aOrigMapMode);
    mpVDev->EnableMapMode(bOrigMapMapModeEnabled);
    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( u"com.sun.star.text.Paragraph"_ustr ) )
        {
            mrCurrentTextParagraph.set( xTextContent );
            Reference< XPropertySet > xPropSet( xTextContent, UNO_QUERY_THROW );
            Reference< XPropertySetInfo > xPropSetInfo = xPropSet->getPropertySetInfo();
            if( xPropSetInfo->hasPropertyByName( u"NumberingLevel"_ustr ) )
            {
                sal_Int16 nListLevel = 0;
                if( xPropSet->getPropertyValue( u"NumberingLevel"_ustr ) >>= nListLevel )
                {
                    mbIsNewListItem = true;
#if OSL_DEBUG_LEVEL > 0
                    sInfo = "NumberingLevel: " + OUString::number( nListLevel );
                    mrExport.AddAttribute( XML_NAMESPACE_NONE, u"style"_ustr, sInfo );
#endif
                    Reference< XIndexReplace > xNumRules;
                    if( xPropSetInfo->hasPropertyByName( u"NumberingRules"_ustr ) )
                    {
                        xPropSet->getPropertyValue( u"NumberingRules"_ustr ) >>= xNumRules;
                    }
                    if( xNumRules.is() && ( nListLevel < xNumRules->getCount() ) )
                    {
                        bool bIsNumbered = true;
                        OUString sNumberingIsNumber(u"NumberingIsNumber"_ustr);
                        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, u"is-numbered"_ustr, 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, u"bullet-char"_ustr, 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( u"com.sun.star.text.Table"_ustr ) )
        {
            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, u"class"_ustr, sInfo );
        SvXMLElementExport aParaElem( mrExport, XML_NAMESPACE_NONE, u"desc"_ustr, 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, u"class"_ustr, u"TextParagraph"_ustr );
                mrExport.AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, rParagraphId );
                mpTextParagraphElem.reset(new SvXMLElementExport( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS ));
            }
            else
            {
                mrExport.AddAttribute( XML_NAMESPACE_NONE, u"id"_ustr, 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( u"TextPortionType"_ustr ) )
        return true;
 
#if OSL_DEBUG_LEVEL > 0
    OUString sInfo;
    OUString sPortionType;
    if( xPortionPropSet->getPropertyValue( u"TextPortionType"_ustr ) >>= 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( u"TextField"_ustr ) )
        {
            Reference < XTextField > xTextField( xRangePropSet->getPropertyValue( u"TextField"_ustr ), UNO_QUERY );
            if( xTextField.is() )
            {
                static constexpr OUString sServicePrefix(u"com.sun.star.text.textfield."_ustr);
                static constexpr OUString sPresentationServicePrefix(u"com.sun.star.presentation.TextField."_ustr);
 
                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 handles Date or Time text field inserted by the user
                // on both page/master page. It doesn't handle the standard DateTime field.
                if( sFieldName == "DateTime" )
                {
                    Reference<XPropertySet> xTextFieldPropSet(xTextField, UNO_QUERY);
                    if( xTextFieldPropSet.is() )
                    {
                        Reference<XPropertySetInfo> xPropSetInfo = xTextFieldPropSet->getPropertySetInfo();
                        if( xPropSetInfo.is() )
                        {
                            // The standard DateTime 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(u"IsFixed"_ustr) && ( ( xTextFieldPropSet->getPropertyValue( u"IsFixed"_ustr ) ) >>= bIsFixed ) && !bIsFixed )
                            {
                                bool bIsDate = true;
                                if( xPropSetInfo->hasPropertyByName(u"IsDate"_ustr) && ( ( xTextFieldPropSet->getPropertyValue( u"IsDate"_ustr ) ) >>= bIsDate ) )
                                {
                                    msDateTimeType = bIsDate ? u"Date"_ustr : u"Time"_ustr;
                                }
                            }
                        }
                    }
                }
                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, u"class"_ustr, u"TextPortion"_ustr );
    SvXMLElementExport aPortionElem( mrExport, XML_NAMESPACE_NONE, u"desc"_ustr, 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, u"class"_ustr, u"SVGTextShape"_ustr );
 
        // 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, u"ooo:numbering-type"_ustr, sNumberingType );
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"ListItem"_ustr );
    }
    else
    {
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"TextParagraph"_ustr );
    }
    maParentFont = vcl::Font();
    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, u"class"_ustr, u"TextPosition"_ustr );
    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(); }
 
OUString& SVGTextWriter::getTextOpacity() { return maTextOpacity; }
 
void SVGTextWriter::implExportHyperlinkIds()
{
    if( !msHyperlinkIdList.isEmpty() )
    {
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"HyperlinkIdList"_ustr );
        SvXMLElementExport aDescElem( mrExport, XML_NAMESPACE_NONE, u"desc"_ustr, true, false );
        mrExport.GetDocHandler()->characters( msHyperlinkIdList.trim() );
        msHyperlinkIdList.clear();
    }
}
 
 
void SVGTextWriter::implWriteBulletChars()
{
    if( maBulletListItemMap.empty() )
        return;
 
    mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BulletChars"_ustr );
    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, u"id"_ustr, sId );
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BulletChar"_ustr );
        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, u"transform"_ustr, 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, u"transform"_ustr, 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, u"use"_ustr, 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, u"d"_ustr, aPathString);
                    SvXMLElementExport aPath(mrExport, XML_NAMESPACE_NONE, u"path"_ustr, 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, u"id"_ustr, sId );
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BitmapPlaceholder"_ustr );
        SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
    }
    endTextPosition();
}
 
 
void SVGTextWriter::implWriteEmbeddedBitmaps()
{
    if( !(mpTextEmbeddedBitmapMtf && mpTextEmbeddedBitmapMtf->GetActionSize()) )
        return;
 
    mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"EmbeddedBitmaps"_ustr );
    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, u"id"_ustr, sId );
            mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"EmbeddedBitmap"_ustr );
 
            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, u"use"_ustr, 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( u"TextField"_ustr ), 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, u"id"_ustr, sId );
                mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"BulletPlaceholder"_ustr );
                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, u"id"_ustr, rTextPortionId );
    }
 
    if( mbIsPlaceholderShape )
    {
        OUString sClass = u"PlaceholderText"_ustr;
        // This case handles Date or Time text field inserted by the user
        // on both page/master page. It doesn't handle the standard DateTime field.
        if( !msDateTimeType.isEmpty() )
        {
            sClass += " " + msDateTimeType;
        }
        else if( !msTextFieldType.isEmpty() )
        {
            sClass += " " + msTextFieldType;
        }
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, sClass );
    }
 
    addFontAttributes( /* isTexTContainer: */ false );
 
    if (!maTextOpacity.isEmpty())
    {
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"fill-opacity"_ustr, 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, u"class"_ustr, u"UrlField"_ustr );
        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, u"a"_ustr, mbIWS, mbIWS );
            mrExport.GetDocHandler()->characters( rText );
        }
    }
    else if ( !msPageCount.isEmpty() )
    {
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"class"_ustr, u"PageCount"_ustr );
        SvXMLElementExport aSVGTspanElem( mrExport, XML_NAMESPACE_NONE, aXMLElemTspan, mbIWS, mbIWS );
        mrExport.GetDocHandler()->characters( msPageCount );
    }
    else
    {
        // Without the following attribute Google Chrome does not render leading spaces
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"style"_ustr, u"white-space: pre"_ustr );
 
        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( auto const& poly : rPolyPoly )
    {
        rDstPolyPoly.Insert( ImplMap( poly, aPoly ) );
    }
 
    return rDstPolyPoly;
}
 
 
OUString SVGActionWriter::GetPathString( const tools::PolyPolygon& rPolyPoly, bool bLine )
{
    OUStringBuffer   aPathData;
    static constexpr OUString   aBlank( u" "_ustr );
    static constexpr OUString   aComma( u","_ustr );
    Point                      aPolyPoint;
 
 
    for( auto rPolyIter = rPolyPoly.begin(); rPolyIter != rPolyPoly.end(); ++rPolyIter )
    {
        auto const& rPoly = *rPolyIter;
        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( rPolyIter != rPolyPoly.end() )
                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, u"line"_ustr, 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, u"rect"_ustr, 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, u"ellipse"_ustr, 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, u"miter"_ustr);
            break;
        }
        case basegfx::B2DLineJoin::Bevel:
        {
            mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, u"bevel"_ustr);
            break;
        }
        case basegfx::B2DLineJoin::Round:
        {
            mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, u"round"_ustr);
            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, u"round"_ustr);
            break;
        }
        case css::drawing::LineCap_SQUARE:
        {
            mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, u"square"_ustr);
            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, u"d"_ustr, GetPathString( aPolyPoly, bLineOnly ) );
 
    {
        // write polyline/polygon element
        SvXMLElementExport aElem( mrExport, XML_NAMESPACE_NONE, u"path"_ustr, true, true );
    }
}
 
 
void SVGActionWriter::ImplWriteShape( const SVGShapeDescriptor& rShape )
{
    tools::PolyPolygon aPolyPoly;
 
    ImplMap( rShape.maShapePolyPoly, aPolyPoly );
 
    const bool bLineOnly
        = (rShape.maShapeFillColor == COL_TRANSPARENT) && (!rShape.moShapeGradient);
    tools::Rectangle   aBoundRect( aPolyPoly.GetBoundRect() );
 
    maAttributeWriter.AddPaintAttr( rShape.maShapeLineColor, rShape.maShapeFillColor, &aBoundRect,
                                   rShape.moShapeGradient ? &*rShape.moShapeGradient : nullptr );
 
    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, u"bevel"_ustr);
            break;
        }
        case basegfx::B2DLineJoin::Round:
        {
            mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinejoin, u"round"_ustr);
            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, u"round"_ustr);
            break;
        }
        case css::drawing::LineCap_SQUARE:
        {
            mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrStrokeLinecap, u"square"_ustr);
            break;
        }
    }
 
    if( !rShape.maDashArray.empty() )
    {
        OUStringBuffer   aDashArrayStr;
 
        for( size_t k = 0; k < rShape.maDashArray.size(); ++k )
        {
            const sal_Int32 nDash = ImplMap(basegfx::fround(rShape.maDashArray[k]));
 
            if( k )
                aDashArrayStr.append(",");
 
            aDashArrayStr.append( nDash );
        }
 
        mrExport.AddAttribute( XML_NAMESPACE_NONE, u"stroke-dasharray"_ustr, 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, u"clipPathUnits"_ustr, u"userSpaceOnUse"_ustr );
        SvXMLElementExport aElemClipPath( mrExport, XML_NAMESPACE_NONE, u"clipPath"_ustr, 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, u"clip-path"_ustr, 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, u"patternUnits"_ustr, u"userSpaceOnUse"_ustr );
 
        {
            SvXMLElementExport aElemPattern( mrExport, XML_NAMESPACE_NONE, u"pattern"_ustr, 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, u""_ustr );
            }
        }
    }
 
    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, const basegfx::BColorStops* pColorStops)
{
    if ( rGradient.GetStyle() == css::awt::GradientStyle_LINEAR ||
         rGradient.GetStyle() == css::awt::GradientStyle_AXIAL )
    {
        ImplWriteGradientLinear( rPolyPoly, rGradient, pColorStops );
    }
    else
    {
        ImplWritePattern( rPolyPoly, nullptr, &rGradient, nWriteFlags );
    }
}
 
 
void SVGActionWriter::ImplWriteGradientLinear( const tools::PolyPolygon& rPolyPoly,
                                               const Gradient& rGradient, const basegfx::BColorStops* pColorStops )
{
    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,
                                   u"userSpaceOnUse"_ustr );
        }
 
        {
            SvXMLElementExport aElemLinearGradient( mrExport, XML_NAMESPACE_NONE, aXMLElemLinearGradient, true, true );
            basegfx::BColorStops aColorStops;
 
            if (nullptr != pColorStops && pColorStops->size() > 1)
            {
                // if we got the real colr stops, use them. That way we are
                // now capable in the SVG export to export real multi color gradients
                aColorStops = *pColorStops;
            }
            else
            {
                // else create color stops with 'old' start/endColor
                aColorStops.emplace_back(0.0, rGradient.GetStartColor().getBColor());
                aColorStops.emplace_back(1.0, rGradient.GetEndColor().getBColor());
            }
 
            // create a basegfx::BGradient with the info to be able to directly
            // use the tooling it offers
            basegfx::BGradient aGradient(
                aColorStops,
                rGradient.GetStyle(),
                rGradient.GetAngle(),
                rGradient.GetOfsX(),
                rGradient.GetOfsY(),
                rGradient.GetBorder(),
                rGradient.GetStartIntensity(),
                rGradient.GetEndIntensity(),
                rGradient.GetSteps());
 
            // apply Start/EndIntensity to the whole color stops - if used
            aGradient.tryToApplyStartEndIntensity();
 
            // apply border to color stops - if used
            aGradient.tryToApplyBorder();
 
            // convert from 'axial' to linear - if needed and used
            aGradient.tryToApplyAxial();
 
            // apply 'Steps' as hard gradient stops - if used
            aGradient.tryToApplySteps();
 
            // write prepared gradient stops
            for (const auto& rCand : aGradient.GetColorStops())
            {
                ImplWriteGradientStop(
                    Color(rCand.getStopColor()),
                    rCand.getStopOffset());
                    //  aStartColor, 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);
}
 
 
void SVGActionWriter::StartMask(const Point& rDestPt, const Size& rDestSize,
                                const Gradient& rGradient, sal_uInt32 nWriteFlags,
                                const basegfx::BColorStops* pColorStops, 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, u"mask"_ustr, 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);
 
                // tdf#155479 prep local ColorStops. The code above
                // implies that the ColorStops need to be reversed,
                // so do so & use change of local ptr to represent this
                basegfx::BColorStops aLocalColorStops;
 
                if (nullptr != pColorStops)
                {
                    aLocalColorStops = *pColorStops;
                    aLocalColorStops.reverseColorStops();
                    pColorStops = &aLocalColorStops;
                }
 
                ImplWriteGradientEx(aPolyPolygon, aGradient, nWriteFlags, pColorStops);
            }
        }
 
        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, const basegfx::BColorStops* pColorStops)
{
    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(basegfx::fround<tools::Long>(aSrcPt.X() * fScaleX));
        aSrcPt.setY(basegfx::fround<tools::Long>(aSrcPt.Y() * fScaleY));
    }
 
    nMoveX = rDestPt.X() - aSrcPt.X();
    nMoveY = rDestPt.Y() - aSrcPt.Y();
 
    if (nMoveX || nMoveY)
        rMtf.Move(nMoveX, nMoveY);
 
    std::optional<OUString> oTextOpacity;
    if (maTextWriter.isTextShapeStarted())
    {
        // We're inside <text>, then try to use the fill-opacity attribute instead of a <g> element
        // to express transparency to ensure well-formed output.
        oTextOpacity = maTextWriter.getTextOpacity();
        StartMask(rDestPt, rDestSize, rGradient, nWriteFlags, pColorStops, &maTextWriter.getTextOpacity());
    }
 
    {
        std::unique_ptr<SvXMLElementExport> pElemG;
        if (!maTextWriter.hasTextOpacity())
        {
            StartMask(rDestPt, rDestSize, rGradient, nWriteFlags, pColorStops);
            pElemG.reset(
                new SvXMLElementExport(mrExport, XML_NAMESPACE_NONE, aXMLElemG, true, true));
        }
 
        mpVDev->Push();
        ImplWriteActions( rMtf, nWriteFlags, u""_ustr );
        mpVDev->Pop();
    }
 
    if (oTextOpacity)
    {
        maTextWriter.getTextOpacity() = *oTextOpacity;
    }
}
 
 
void SVGActionWriter::ImplWriteText( const Point& rPos, const OUString& rText,
                                     KernArraySpan 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,
                                     KernArraySpan 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 );
 
    KernArray aTmpArray;
    // get text sizes
    if( !pDXArray.empty() )
    {
        aNormSize = Size( mpVDev->GetTextWidth( rText ), 0 );
        aTmpArray.assign(pDXArray.begin(), pDXArray.end());
    }
    else
    {
        aNormSize
            = Size(basegfx::fround<tools::Long>(mpVDev->GetTextArray(rText, &aTmpArray).nWidth), 0);
    }
 
    // 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, u"class"_ustr, u"PlaceholderText"_ustr );
            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( aTmpArray[ 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++ )
                    aTmpArray[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 = aTmpArray[ 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>( basegfx::fround<tools::Long>( aMetric.GetLineHeight() * 0.05 ), 1 );
 
    if( rFont.GetStrikeout() )
    {
        const tools::Long nYLinePos = aBaseLinePos.Y() - basegfx::fround<tools::Long>( 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( tools::PolyPolygon(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( tools::PolyPolygon(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(u"Graphic"_ustr))
    {
        xPropertySet->getPropertyValue(u"Graphic"_ustr) >>= 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, u"use"_ustr, 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 == rBmpEx)
            {
                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() ) );
 
    // If we have a media object (a video), export the video.
    // Also, use the image generated above as the video poster (thumbnail).
    SdrMediaObj* pMediaObj
        = pShape ? dynamic_cast<SdrMediaObj*>(SdrObject::getSdrObjectFromXShape(*pShape)) : nullptr;
    const bool embedVideo = (pMediaObj && !pMediaObj->getTempURL().isEmpty());
 
    if (!embedVideo)
    {
        // the image must be scaled to aSz in a non-uniform way
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"preserveAspectRatio"_ustr, u"none"_ustr);
 
        mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrXLinkHRef, aBuffer.makeStringAndClear());
 
        SvXMLElementExport aElem(mrExport, XML_NAMESPACE_NONE, u"image"_ustr, true, true);
    }
    else
    {
        // <foreignObject xmlns="http://www.w3.org/2000/svg" overflow="visible" width="499.6" height="374.33333333333337" x="705" y="333">
        //     <body xmlns="http://www.w3.org/1999/xhtml">
        //         <video controls="controls" width="499.6" height="374.33333333333337">
        //             <source src="file:///tmp/abcdef.mp4" type="video/mp4">
        //         </video>
        //     </body>
        // </foreignObject>
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"xmlns"_ustr, u"http://www.w3.org/2000/svg"_ustr);
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"overflow"_ustr, u"visible"_ustr);
        SvXMLElementExport aForeignObject(mrExport, XML_NAMESPACE_NONE, u"foreignObject"_ustr, true,
                                          true);
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"xmlns"_ustr, u"http://www.w3.org/1999/xhtml"_ustr);
        SvXMLElementExport aBody(mrExport, XML_NAMESPACE_NONE, u"body"_ustr, true, true);
 
        mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrWidth, OUString::number(aSz.Width()));
        mrExport.AddAttribute(XML_NAMESPACE_NONE, aXMLAttrHeight, OUString::number(aSz.Height()));
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"autoplay"_ustr, u"autoplay"_ustr);
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"controls"_ustr, u"controls"_ustr);
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"loop"_ustr, u"loop"_ustr);
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"preload"_ustr, u"auto"_ustr);
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"poster"_ustr, aBuffer.makeStringAndClear());
        SvXMLElementExport aVideo(mrExport, XML_NAMESPACE_NONE, u"video"_ustr, true, true);
 
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"src"_ustr, pMediaObj->getTempURL());
        mrExport.AddAttribute(XML_NAMESPACE_NONE, u"type"_ustr, u"video/mp4"_ustr); //FIXME: set mime type.
        SvXMLElementExport aSource(mrExport, XML_NAMESPACE_NONE, u"source"_ustr, 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, u"desc"_ustr, 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(OUString::Concat("; ")
                                    + std::u16string_view(
                                        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( tools::PolyPolygon(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( tools::PolyPolygon(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, nullptr );
                }
            }
            break;
 
            case MetaActionType::GRADIENTEX:
            {
                if( nWriteFlags & SVGWRITER_WRITE_FILL )
                {
                    const MetaGradientExAction* pA = static_cast<const MetaGradientExAction*>(pAction);
                    ImplWriteGradientEx( pA->GetPolyPolygon(), pA->GetGradient(), nWriteFlags, nullptr );
                }
            }
            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 - basegfx::fround<sal_uInt8>( pA->GetTransparence() * 2.55 ) );
                        aNewFillColor.SetAlpha( 255 - basegfx::fround<sal_uInt8>( 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, pA->getSVGTransparencyColorStops()  );
                }
            }
            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("BGRAD_SEQ_BEGIN"))
                {
                    // detect and use the new BGRAD_SEQ_* metafile comment actions
                    const MetaGradientExAction* pGradAction(nullptr);
                    bool bDone(false);
 
                    while (!bDone && (++nCurAction < nCount))
                    {
                        pAction = rMtf.GetAction(nCurAction);
 
                        if (MetaActionType::GRADIENTEX == pAction->GetType())
                        {
                            // remember the 'paint' data action
                            pGradAction = static_cast<const MetaGradientExAction*>(pAction);
                        }
                        else if (MetaActionType::COMMENT == pAction->GetType()
                            && static_cast<const MetaCommentAction*>(pAction)->GetComment().equalsIgnoreAsciiCase("BGRAD_SEQ_END"))
                        {
                            // end action found
                            bDone = true;
                        }
                    }
 
                    if (nullptr != pGradAction)
                    {
                        // we have a complete actions sequence of BGRAD_SEQ_*, so we can now
                        // read the correct color stops here
                        basegfx::BColorStops aColorStops;
                        SvMemoryStream aMemStm(const_cast<sal_uInt8 *>(pA->GetData()), pA->GetDataSize(), StreamMode::READ);
                        VersionCompatRead aCompat(aMemStm);
                        sal_uInt16 nTmp(0);
                        double fOff, fR, fG, fB;
                        aMemStm.ReadUInt16( nTmp );
 
                        const size_t nMaxPossibleEntries = aMemStm.remainingSize() / 4 * sizeof(double);
                        if (nTmp > nMaxPossibleEntries)
                        {
                            SAL_WARN("filter.svg", "gradient record claims to have: " << nTmp << " entries, but only " << nMaxPossibleEntries << " possible, clamping");
                            nTmp = nMaxPossibleEntries;
                        }
 
                        for (sal_uInt16 a(0); a < nTmp; a++)
                        {
                            aMemStm.ReadDouble(fOff);
                            aMemStm.ReadDouble(fR);
                            aMemStm.ReadDouble(fG);
                            aMemStm.ReadDouble(fB);
 
                            aColorStops.emplace_back(fOff, basegfx::BColor(fR, fG, fB));
                        }
 
                        // export with real Color Stops
                        ImplWriteGradientEx(pGradAction->GetPolyPolygon(), pGradAction->GetGradient(), nWriteFlags, &aColorStops);
                    }
                }
                else 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, nullptr );
                }
                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 = std::move(aShapePolyPoly);
                            mapCurShape->maShapeFillColor = aFill.getFillColor();
                            mapCurShape->maShapeFillColor.SetAlpha( 255 - basegfx::fround<sal_uInt8>( 255.0 * aFill.getTransparency() ) );
 
                            if( bGradient )
                            {
                                // step through following actions until the first Gradient/GradientEx action is found
                                while (!mapCurShape->moShapeGradient && 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->moShapeGradient.emplace(
                                            static_cast< const MetaGradientExAction* >( pAction )->GetGradient() );
                                    }
                                    else if( pAction->GetType() == MetaActionType::GRADIENT )
                                    {
                                        mapCurShape->moShapeGradient.emplace(
                                            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 = tools::PolyPolygon(aPoly);
                    }
 
                    mapCurShape->maShapeLineColor = mpVDev->GetLineColor();
                    mapCurShape->maShapeLineColor.SetAlpha( 255 - basegfx::fround<sal_uInt8>( aStroke.getTransparency() * 255.0 ) );
                    mapCurShape->mnStrokeWidth = basegfx::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 = std::move(aStartArrow);
 
                            if( bUseElementId ) // #i124825# aElementId is optional, may be zero
                            {
                                mapCurShape->maId = aElementId + "_" + OUString::number(nEntryCount++);
                            }
 
                            ImplWriteShape( *mapCurShape );
                        }
 
                        if(aEndArrow.Count())
                        {
                            mapCurShape->maShapePolyPoly = std::move(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, u"use"_ustr, 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 aClipRegion = mpVDev->GetActiveClipRegion();
                ImplWriteClipPath( aClipRegion.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 u"com.sun.star.comp.Draw.SVGWriter"_ustr;
}
css::uno::Sequence< OUString > SVGWriter::getSupportedServiceNames()
{
    return { u"com.sun.star.svg.SVGWriter"_ustr };
}
 
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: */

V530 The return value of function 'registerReference' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V530 The return value of function 'ImplMap' is required to be utilized.

V547 Expression 'NumberingType::BITMAP == meNumberingType' is always false.

V547 Expression is always true.

V506 Pointer to local variable 'aLocalColorStops' is stored outside the scope of this variable. Such a pointer will become invalid.

V560 A part of conditional expression is always false: !bIsFixed.

V1019 Compound assignment expression is used inside condition.

V1019 Compound assignment expression is used inside condition.