/* -*- 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 <mtftools.hxx>

#include <cstdlib>
#include <memory>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <vcl/metric.hxx>
#include <vcl/graphictools.hxx>
#include <vcl/BitmapTools.hxx>
#include <vcl/metaact.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/svapp.hxx>
#include <tools/stream.hxx>
#include <rtl/tencinfo.h>
#include <sal/log.hxx>
#include <osl/diagnose.h>
#include <vcl/virdev.hxx>
#include <o3tl/safeint.hxx>
#include <comphelper/configuration.hxx>
#include <unotools/defaultencoding.hxx>
#include <unotools/wincodepage.hxx>

#if OSL_DEBUG_LEVEL > 1
#define EMFP_DEBUG(x) x
#else
#define EMFP_DEBUG(x)
#endif

namespace emfio
{
    SvStream& operator >> (SvStream& rInStream, XForm& rXForm)
    {
        if (sizeof(float) != 4)
        {
            OSL_FAIL("EmfReader::sizeof( float ) != 4");
            rXForm = XForm();
        }
        else
        {
            rInStream.ReadFloat(rXForm.eM11);
            rInStream.ReadFloat(rXForm.eM12);
            rInStream.ReadFloat(rXForm.eM21);
            rInStream.ReadFloat(rXForm.eM22);
            rInStream.ReadFloat(rXForm.eDx);
            rInStream.ReadFloat(rXForm.eDy);
            if (std::isnan(rXForm.eM11) ||
                std::isnan(rXForm.eM12) ||
                std::isnan(rXForm.eM21) ||
                std::isnan(rXForm.eM22) ||
                std::isnan(rXForm.eDx) ||
                std::isnan(rXForm.eDy))
            {
                SAL_WARN("emfio", "XForm member isnan, ignoring");
                rXForm = XForm();
            }
        }
        return rInStream;
    }

    void WinMtfClipPath::intersectClip( const basegfx::B2DPolyPolygon& rPolyPolygon )
    {
        maClip.intersectPolyPolygon(rPolyPolygon);
    }

    void WinMtfClipPath::excludeClip( const basegfx::B2DPolyPolygon& rPolyPolygon )
    {
        maClip.subtractPolyPolygon(rPolyPolygon);
    }

    void WinMtfClipPath::setClipPath( const basegfx::B2DPolyPolygon& rB2DPoly, RegionMode nClippingMode )
    {
        switch ( nClippingMode )
        {
            case RegionMode::RGN_OR :
                maClip.unionPolyPolygon(rB2DPoly);
                break;
            case RegionMode::RGN_XOR :
                maClip.xorPolyPolygon(rB2DPoly);
                break;
            case RegionMode::RGN_DIFF :
                maClip.subtractPolyPolygon(rB2DPoly);
                break;
            case RegionMode::RGN_AND :
                maClip.intersectPolyPolygon(rB2DPoly);
                break;
            case RegionMode::RGN_COPY :
                maClip = basegfx::utils::B2DClipState(rB2DPoly);
                break;
        }
    }

    void WinMtfClipPath::moveClipRegion( const Size& rSize )
    {
        basegfx::B2DHomMatrix aTranslate;
        aTranslate.translate(rSize.Width(), rSize.Height());
        maClip.transform(aTranslate);
    }

    void WinMtfClipPath::setDefaultClipPath()
    {
        // Empty clip region - everything visible
        maClip = basegfx::utils::B2DClipState();
    }

    basegfx::B2DPolyPolygon const & WinMtfClipPath::getClipPath() const
    {
        return maClip.getClipPoly();
    }

    void WinMtfPathObj::AddPoint( const Point& rPoint )
    {
        if ( bClosed )
            Insert( tools::Polygon() );
        tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ];
        rPoly.Insert( rPoly.GetSize(), rPoint );
        bClosed = false;
    }

    void WinMtfPathObj::AddPolyLine( const tools::Polygon& rPolyLine )
    {
        if ( bClosed )
            Insert( tools::Polygon() );
        tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ];
        rPoly.Insert( rPoly.GetSize(), rPolyLine );
        bClosed = false;
    }

    void WinMtfPathObj::AddPolygon( const tools::Polygon& rPoly )
    {
        Insert( rPoly );
        bClosed = true;
    }

    void WinMtfPathObj::AddPolyPolygon( const tools::PolyPolygon& rPolyPoly )
    {
        sal_uInt16 i, nCount = rPolyPoly.Count();
        for ( i = 0; i < nCount; i++ )
            Insert( rPolyPoly[ i ] );
        bClosed = true;
    }

    void WinMtfPathObj::ClosePath()
    {
        if ( Count() )
        {
            tools::Polygon& rPoly = static_cast<tools::PolyPolygon&>(*this)[ Count() - 1 ];
            if ( rPoly.GetSize() > 2 )
            {
                Point aFirst( rPoly[ 0 ] );
                if ( aFirst != rPoly[ rPoly.GetSize() - 1 ] )
                    rPoly.Insert( rPoly.GetSize(), aFirst );
            }
        }
        bClosed = true;
    }

    WinMtfFontStyle::WinMtfFontStyle( LOGFONTW const & rFont )
    {
        rtl_TextEncoding eCharSet;
        if ((rFont.alfFaceName == "Symbol")
         || (rFont.alfFaceName == "MT Extra"))
            eCharSet = RTL_TEXTENCODING_SYMBOL;
        else if ((rFont.lfCharSet == DEFAULT_CHARSET) || (rFont.lfCharSet == OEM_CHARSET))
            eCharSet = utl_getWinTextEncodingFromLangStr(utl_getLocaleForGlobalDefaultEncoding(),
                                                         rFont.lfCharSet == OEM_CHARSET);
        else
            eCharSet = rtl_getTextEncodingFromWindowsCharset( rFont.lfCharSet );
        if ( eCharSet == RTL_TEXTENCODING_DONTKNOW )
            eCharSet = RTL_TEXTENCODING_MS_1252;
        aFont.SetCharSet( eCharSet );
        aFont.SetFamilyName( rFont.alfFaceName );
        FontFamily eFamily;
        switch ( rFont.lfPitchAndFamily >> 4 & 0x0f )
        {
            case FamilyFont::FF_ROMAN:
                eFamily = FAMILY_ROMAN;
            break;

            case FamilyFont::FF_SWISS:
                eFamily = FAMILY_SWISS;
            break;

            case FamilyFont::FF_MODERN:
                eFamily = FAMILY_MODERN;
            break;

            case FamilyFont::FF_SCRIPT:
                eFamily = FAMILY_SCRIPT;
            break;

            case FamilyFont::FF_DECORATIVE:
                 eFamily = FAMILY_DECORATIVE;
            break;

            default:
                eFamily = FAMILY_DONTKNOW;
            break;
        }
        aFont.SetFamily( eFamily );

        FontPitch ePitch;
        switch ( rFont.lfPitchAndFamily & 0x0f )
        {
            case FIXED_PITCH:
                ePitch = PITCH_FIXED;
            break;

            case DEFAULT_PITCH:
            case VARIABLE_PITCH:
            default:
                ePitch = PITCH_VARIABLE;
            break;
        }
        aFont.SetPitch( ePitch );

        FontWeight eWeight;
        if (rFont.lfWeight == 0) // default weight SHOULD be used
            eWeight = WEIGHT_DONTKNOW;
        else if (rFont.lfWeight <= FW_THIN)
            eWeight = WEIGHT_THIN;
        else if( rFont.lfWeight <= FW_ULTRALIGHT )
            eWeight = WEIGHT_ULTRALIGHT;
        else if( rFont.lfWeight <= FW_LIGHT )
            eWeight = WEIGHT_LIGHT;
        else if( rFont.lfWeight <  FW_MEDIUM )
            eWeight = WEIGHT_NORMAL;
        else if( rFont.lfWeight == FW_MEDIUM )
            eWeight = WEIGHT_MEDIUM;
        else if( rFont.lfWeight <= FW_SEMIBOLD )
            eWeight = WEIGHT_SEMIBOLD;
        else if( rFont.lfWeight <= FW_BOLD )
            eWeight = WEIGHT_BOLD;
        else if( rFont.lfWeight <= FW_ULTRABOLD )
            eWeight = WEIGHT_ULTRABOLD;
        else
            eWeight = WEIGHT_BLACK;
        aFont.SetWeight( eWeight );

        if( rFont.lfItalic )
            aFont.SetItalic( ITALIC_NORMAL );

        if( rFont.lfUnderline )
            aFont.SetUnderline( LINESTYLE_SINGLE );

        if( rFont.lfStrikeOut )
            aFont.SetStrikeout( STRIKEOUT_SINGLE );

        aFont.SetOrientation( Degree10(static_cast<sal_Int16>(rFont.lfEscapement)) );

        Size aFontSize( rFont.lfWidth, rFont.lfHeight );
        if ( rFont.lfHeight > 0 )
        {
            // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
            SolarMutexGuard aGuard;
            ScopedVclPtrInstance< VirtualDevice > pVDev;
            // converting the cell height into a font height
            aFont.SetFontSize( aFontSize );
            pVDev->SetFont( aFont );
            FontMetric aMetric( pVDev->GetFontMetric() );
            tools::Long nHeight = static_cast<tools::Long>(aMetric.GetAscent()) + aMetric.GetDescent();
            if (nHeight)
            {
                double fHeight = (static_cast<double>(aFontSize.Height()) * rFont.lfHeight ) / nHeight;
                aFontSize.setHeight( static_cast<sal_Int32>( fHeight + 0.5 ) );
            }
        }

        // Convert height to positive
        aFontSize.setHeight( std::abs(aFontSize.Height()) );
        aFont.SetFontSize(aFontSize);

        // tdf#127471 adapt nFontWidth from Windows-like notation to
        // NormedFontScaling if used for text scaling
#ifndef _WIN32
        const bool bFontScaledHorizontally(aFontSize.Width() != 0 && aFontSize.Width() != aFontSize.Height());

        if(bFontScaledHorizontally)
        {
            // tdf#127471 nFontWidth is the Windows FontScaling, need to convert to
            // Non-Windowslike notation relative to FontHeight.
            const tools::Long nAverageFontWidth(aFont.GetOrCalculateAverageFontWidth());

            if(nAverageFontWidth > 0)
            {
                const double fScaleFactor(static_cast<double>(aFontSize.Height()) / static_cast<double>(nAverageFontWidth));
                aFont.SetAverageFontWidth(static_cast<tools::Long>(static_cast<double>(aFontSize.Width()) * fScaleFactor));
            }
        }
#endif
    };

    WinMtfFontStyle::~WinMtfFontStyle() = default;

    // tdf#127471
    ScaledFontDetectCorrectHelper::ScaledFontDetectCorrectHelper()
    {
    }

    void ScaledFontDetectCorrectHelper::endCurrentMetaFontAction()
    {
        if(maCurrentMetaFontAction.is() && !maAlternativeFontScales.empty())
        {
            // create average corrected FontScale value and count
            // positive/negative hits
            sal_uInt32 nPositive(0);
            sal_uInt32 nNegative(0);
            double fAverage(0.0);

            for(double fPart : maAlternativeFontScales)
            {
                if(fPart < 0.0)
                {
                    nNegative++;
                    fAverage += -fPart;
                }
                else
                {
                    nPositive++;
                    fAverage += fPart;
                }
            }

            fAverage /= static_cast<double>(maAlternativeFontScales.size());

            if(nPositive >= nNegative)
            {
                // correction intended, it is probably an old imported file
                maPositiveIdentifiedCases.emplace_back(maCurrentMetaFontAction, fAverage);
            }
            else
            {
                // correction not favorable in the majority of cases for this Font, still
                // remember to have a weight in the last decision for correction
                maNegativeIdentifiedCases.emplace_back(maCurrentMetaFontAction, fAverage);
            }
        }

        maCurrentMetaFontAction.clear();
        maAlternativeFontScales.clear();
    }

    void ScaledFontDetectCorrectHelper::newCurrentMetaFontAction(const rtl::Reference<MetaFontAction>& rNewMetaFontAction)
    {
        maCurrentMetaFontAction.clear();
        maAlternativeFontScales.clear();

        if(!rNewMetaFontAction.is())
            return;

        // check 1st criteria for FontScale active. We usually write this,
        // so this will already sort out most situations
        const vcl::Font& rCandidate(rNewMetaFontAction->GetFont());

        if(0 != rCandidate.GetAverageFontWidth())
        {
            const tools::Long nUnscaledAverageFontWidth(rCandidate.GetOrCalculateAverageFontWidth());

            // check 2nd (system-dependent) criteria for FontScale
            if(nUnscaledAverageFontWidth != rCandidate.GetFontHeight())
            {
                // FontScale is active, remember and use as current
                maCurrentMetaFontAction = rNewMetaFontAction;
            }
        }
    }

    void ScaledFontDetectCorrectHelper::evaluateAlternativeFontScale(OUString const & rText, tools::Long nImportedTextLength)
    {
        if(!maCurrentMetaFontAction.is())
            return;

        SolarMutexGuard aGuard; // VirtualDevice is not thread-safe
        ScopedVclPtrInstance< VirtualDevice > pTempVirtualDevice;

        // calculate measured TextLength
        const vcl::Font& rFontCandidate(maCurrentMetaFontAction->GetFont());
        pTempVirtualDevice->SetFont(rFontCandidate);
        tools::Long nMeasuredTextLength(pTempVirtualDevice->GetTextWidth(rText));
        // on failure, use original length
        if (!nMeasuredTextLength)
            nMeasuredTextLength = nImportedTextLength;

        // compare expected and imported TextLengths
        if (nImportedTextLength == nMeasuredTextLength)
            return;

        const double fFactorText(static_cast<double>(nImportedTextLength) / static_cast<double>(nMeasuredTextLength));
        const double fFactorTextPercent(fabs(1.0 - fFactorText) * 100.0);

        // if we assume that loaded file was written on old linux, we have to
        // back-convert the scale value depending on which system we run
#ifdef _WIN32
        // When running on Windows the value was not adapted at font import (see WinMtfFontStyle
        // constructor), so it is still NormedFontScaling and we need to convert to Windows-style
        // scaling
#else
        // When running on unx (non-Windows) the value was already adapted at font import (see WinMtfFontStyle
        // constructor). It was wrongly assumed to be Windows-style FontScaling, so we need to revert that
        // to get back to the needed unx-style FontScale
#endif
        // Interestingly this leads to the *same* correction, so no need to make this
        // system-dependent (!)
        const tools::Long nUnscaledAverageFontWidth(rFontCandidate.GetOrCalculateAverageFontWidth());
        const tools::Long nScaledAverageFontWidth(rFontCandidate.GetAverageFontWidth());
        const double fScaleFactor(static_cast<double>(nUnscaledAverageFontWidth) / static_cast<double>(rFontCandidate.GetFontHeight()));
        const double fCorrectedAverageFontWidth(static_cast<double>(nScaledAverageFontWidth) * fScaleFactor);
        tools::Long nCorrectedTextLength(0);

        { // do in own scope, only need nUnscaledAverageFontWidth
            vcl::Font rFontCandidate2(rFontCandidate);
            rFontCandidate2.SetAverageFontWidth(static_cast<tools::Long>(fCorrectedAverageFontWidth));
            pTempVirtualDevice->SetFont(rFontCandidate2);
            nCorrectedTextLength = pTempVirtualDevice->GetTextWidth(rText);
            // on failure, use original length
            if (!nCorrectedTextLength)
                nCorrectedTextLength = nImportedTextLength;
        }

        const double fFactorCorrectedText(static_cast<double>(nImportedTextLength) / static_cast<double>(nCorrectedTextLength));
        const double fFactorCorrectedTextPercent(fabs(1.0 - fFactorCorrectedText) * 100.0);

        // If FactorCorrectedText fits better than FactorText this is probably
        // an import of an old EMF/WMF written by LibreOffice on a non-Windows (unx) system
        // and should be corrected.
        // Usually in tested cases this lies inside 5% of range, so detecting this just using
        //  fFactorTextPercent inside 5% -> no old file
        //  fFactorCorrectedTextPercent inside 5% -> is old file
        // works not too bad, but there are some strange not so often used fonts where that
        // values do deviate, so better just compare if old corrected would fit better than
        // the uncorrected case, that is usually safe.
        if(fFactorCorrectedTextPercent < fFactorTextPercent)
        {
            maAlternativeFontScales.push_back(fCorrectedAverageFontWidth);
        }
        else
        {
            // also push, but negative to remember non-fitting case
            maAlternativeFontScales.push_back(-fCorrectedAverageFontWidth);
        }
    }

    void ScaledFontDetectCorrectHelper::applyAlternativeFontScale()
    {
        // make sure last evtl. detected current FontAction gets added to identified cases
        endCurrentMetaFontAction();

        // Take final decision to correct FontScaling for this imported Metafile or not.
        // It is possible to weight positive against negative cases, so to only finally
        // correct when more positive cases were detected.
        // But that would be inconsequent and wrong. *If* the detected case is an old import
        // the whole file was written with wrong FontScale values and all Font actions
        // need to be corrected. Thus, for now, correct all when there are/is positive
        // cases detected.
        // On the other hand it *may* be that for some strange fonts there is a false-positive
        // in the positive cases, so at least insist on positive cases being more than negative.
        // Still, do then correct *all* cases.
        if(!maPositiveIdentifiedCases.empty()
            && maPositiveIdentifiedCases.size() >= maNegativeIdentifiedCases.size())
        {
            for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maPositiveIdentifiedCases)
            {
                rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second));
            }
            for(std::pair<rtl::Reference<MetaFontAction>, double>& rCandidate : maNegativeIdentifiedCases)
            {
                rCandidate.first->correctFontScale(static_cast<tools::Long>(rCandidate.second));
            }
        }

        maPositiveIdentifiedCases.clear();
        maNegativeIdentifiedCases.clear();
    }

    Color MtfTools::ReadColor()
    {
        sal_uInt32 nColor(0);
        mpInputStream->ReadUInt32( nColor );
        Color aColor( COL_BLACK );
        if ( ( nColor & 0xFFFF0000 ) == 0x01000000 )
        {
            size_t index = nColor & 0x0000FFFF;
            if ( index < maPalette.aPaletteColors.size() )
                aColor = maPalette.aPaletteColors[ index ];
            else
                SAL_INFO( "emfio", "\t\t Palette index out of range: " << index );
        }
        else
            aColor = Color( static_cast<sal_uInt8>( nColor ), static_cast<sal_uInt8>( nColor >> 8 ), static_cast<sal_uInt8>( nColor >> 16 ) );

        SAL_INFO("emfio", "\t\tColor: " << aColor);
        return aColor;
    };

    Point MtfTools::ImplScale(const Point& rPoint) // Hack to set varying defaults for incompletely defined files.
    {
        if (!mbIsMapDevSet)
            return Point(rPoint.X() * UNDOCUMENTED_WIN_RCL_RELATION - mrclFrame.Left(),
                         rPoint.Y() * UNDOCUMENTED_WIN_RCL_RELATION - mrclFrame.Top());
        else
            return rPoint;
    }

    Point MtfTools::ImplMap( const Point& rPt )
    {
        if ( mnWinExtX && mnWinExtY )
        {
            double fX = rPt.X();
            double fY = rPt.Y();

            double fX2 = fX * maXForm.eM11 + fY * maXForm.eM21 + maXForm.eDx;
            double fY2 = fX * maXForm.eM12 + fY * maXForm.eM22 + maXForm.eDy;

            if ( meGfxMode == GraphicsMode::GM_COMPATIBLE )
            {
                fX2 -= mnWinOrgX;
                fY2 -= mnWinOrgY;

                switch( meMapMode )
                {
                    case MappingMode::MM_LOENGLISH :
                    {
                        fX2 = o3tl::convert(fX2, o3tl::Length::in100, o3tl::Length::mm100);
                        fY2 = o3tl::convert(-fY2, o3tl::Length::in100, o3tl::Length::mm100);
                    }
                    break;
                    case MappingMode::MM_HIENGLISH :
                    {
                        fX2 = o3tl::convert(fX2, o3tl::Length::in1000, o3tl::Length::mm100);
                        fY2 = o3tl::convert(-fY2, o3tl::Length::in1000, o3tl::Length::mm100);
                    }
                    break;
                    case MappingMode::MM_TWIPS:
                    {
                        fX2 = o3tl::convert(fX2, o3tl::Length::twip, o3tl::Length::mm100);
                        fY2 = o3tl::convert(-fY2, o3tl::Length::twip, o3tl::Length::mm100);
                    }
                    break;
                    case MappingMode::MM_LOMETRIC :
                    {
                        fX2 = o3tl::convert(fX2, o3tl::Length::mm10, o3tl::Length::mm100);
                        fY2 = o3tl::convert(-fY2, o3tl::Length::mm10, o3tl::Length::mm100);
                    }
                    break;
                    case MappingMode::MM_HIMETRIC : // in hundredth of a millimeter
                    {
                        fY2 *= -1;
                    }
                    break;
                    default :
                    {
                        if (mnPixX == 0 || mnPixY == 0)
                        {
                            SAL_WARN("emfio", "invalid scaling factor");
                            return Point();
                        }
                        else
                        {
                            if ( meMapMode != MappingMode::MM_TEXT )
                            {
                                fX2 /= mnWinExtX;
                                fY2 /= mnWinExtY;
                                fX2 *= mnDevWidth;
                                fY2 *= mnDevHeight;
                            }
                            fX2 *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX);
                            fY2 *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY);
                        }
                    }
                    break;
                }

                double nDevOrgX = mnDevOrgX;
                if (mnPixX)
                    nDevOrgX *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX);
                fX2 += nDevOrgX;
                double nDevOrgY = mnDevOrgY;
                if (mnPixY)
                    nDevOrgY *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY);
                fY2 += nDevOrgY;

                fX2 -= mrclFrame.Left();
                fY2 -= mrclFrame.Top();
            }
            return Point(basegfx::fround<tools::Long>(fX2), basegfx::fround<tools::Long>(fY2));
        }
        else
            return Point();
    };

    Size MtfTools::ImplMap(const Size& rSz, bool bDoWorldTransform)
    {
        if ( mnWinExtX && mnWinExtY )
        {
            // #i121382# apply the whole WorldTransform, else a rotation will be misinterpreted
            double fWidth, fHeight;
            if (bDoWorldTransform)
            {
                fWidth = rSz.Width() * maXForm.eM11 + rSz.Height() * maXForm.eM21;
                fHeight = rSz.Width() * maXForm.eM12 + rSz.Height() * maXForm.eM22;
            }
            else
            {
                //take the scale, but not the rotation
                basegfx::B2DHomMatrix aMatrix(maXForm.eM11, maXForm.eM12, 0,
                                              maXForm.eM21, maXForm.eM22, 0);
                basegfx::B2DTuple aScale, aTranslate;
                double fRotate, fShearX;
                if (!aMatrix.decompose(aScale, aTranslate, fRotate, fShearX))
                {
                    aScale.setX(1.0);
                    aScale.setY(1.0);
                }
                fWidth = rSz.Width() * aScale.getX();
                fHeight = rSz.Height() * aScale.getY();
            }

            if ( meGfxMode == GraphicsMode::GM_COMPATIBLE )
            {
                switch( meMapMode )
                {
                    case MappingMode::MM_LOENGLISH :
                    {
                        fWidth = o3tl::convert(fWidth, o3tl::Length::in100, o3tl::Length::mm100);
                        fHeight = o3tl::convert(-fHeight, o3tl::Length::in100, o3tl::Length::mm100);
                    }
                    break;
                    case MappingMode::MM_HIENGLISH :
                    {
                        fWidth = o3tl::convert(fWidth, o3tl::Length::in1000, o3tl::Length::mm100);
                        fHeight = o3tl::convert(-fHeight, o3tl::Length::in1000, o3tl::Length::mm100);
                    }
                    break;
                    case MappingMode::MM_LOMETRIC :
                    {
                        fWidth = o3tl::convert(fWidth, o3tl::Length::mm10, o3tl::Length::mm100);
                        fHeight = o3tl::convert(-fHeight, o3tl::Length::mm10, o3tl::Length::mm100);
                    }
                    break;
                    case MappingMode::MM_HIMETRIC : // in hundredth of millimeters
                    {
                        fHeight *= -1;
                    }
                    break;
                    case MappingMode::MM_TWIPS:
                    {
                        fWidth = o3tl::convert(fWidth, o3tl::Length::twip, o3tl::Length::mm100);
                        fHeight = o3tl::convert(-fHeight, o3tl::Length::twip, o3tl::Length::mm100);
                    }
                    break;
                    default :
                    {
                        if (mnPixX == 0 || mnPixY == 0)
                        {
                            SAL_WARN("emfio", "invalid scaling factor");
                            return Size();
                        }
                        else
                        {
                            if ( meMapMode != MappingMode::MM_TEXT )
                            {
                                fWidth /= mnWinExtX;
                                fHeight /= mnWinExtY;
                                fWidth *= mnDevWidth;
                                fHeight *= mnDevHeight;
                            }
                            fWidth *= static_cast<double>(mnMillX) * 100.0 / static_cast<double>(mnPixX);
                            fHeight *= static_cast<double>(mnMillY) * 100.0 / static_cast<double>(mnPixY);
                        }
                    }
                    break;
                }
            }
            return Size(basegfx::fround<tools::Long>(fWidth), basegfx::fround<tools::Long>(fHeight));
        }
        else
            return Size();
    }

    tools::Rectangle MtfTools::ImplMap( const tools::Rectangle& rRect )
    {
        tools::Rectangle aRect;
        aRect.SetPos(ImplMap(rRect.TopLeft()));
        aRect.SaturatingSetSize(ImplMap(rRect.GetSize()));
        return aRect;
    }

    void MtfTools::ImplMap( vcl::Font& rFont )
    {
        // !!! HACK: we now always set the width to zero because the OS width is interpreted differently;
        // must later be made portable in SV (KA 1996-02-08)
        Size  aFontSize = ImplMap (rFont.GetFontSize(), false);

        const auto nHeight = aFontSize.Height();
        if (nHeight < 0)
            aFontSize.setHeight( o3tl::saturating_toggle_sign(nHeight) );

        rFont.SetFontSize( aFontSize );

        sal_Int32 nResult;
        const bool bFail = o3tl::checked_multiply(mnWinExtX, mnWinExtY, nResult);
        if (!bFail && nResult < 0)
            rFont.SetOrientation( 3600_deg10 - rFont.GetOrientation() );
    }

    tools::Polygon& MtfTools::ImplMap( tools::Polygon& rPolygon )
    {
        sal_uInt16 nPoints = rPolygon.GetSize();
        for ( sal_uInt16 i = 0; i < nPoints; i++ )
        {
            rPolygon[ i ] = ImplMap( rPolygon[ i ] );
        }
        return rPolygon;
    }

    void MtfTools::ImplScale( tools::Polygon& rPolygon )
    {
        sal_uInt16 nPoints = rPolygon.GetSize();
        for ( sal_uInt16 i = 0; i < nPoints; i++ )
        {
            rPolygon[ i ] = ImplScale( rPolygon[ i ] );
        }
    }

    tools::PolyPolygon& MtfTools::ImplScale( tools::PolyPolygon& rPolyPolygon )
    {
        sal_uInt16 nPolys = rPolyPolygon.Count();
        for (sal_uInt16 i = 0; i < nPolys; ++i)
        {
            ImplScale(rPolyPolygon[i]);
        }
        return rPolyPolygon;
    }

    tools::PolyPolygon& MtfTools::ImplMap( tools::PolyPolygon& rPolyPolygon )
    {
        sal_uInt16 nPolys = rPolyPolygon.Count();
        for ( sal_uInt16 i = 0; i < nPolys; ImplMap( rPolyPolygon[ i++ ] ) ) ;
        return rPolyPolygon;
    }

    void MtfTools::SelectObject( sal_uInt32 nIndex )
    {
        if ( nIndex & ENHMETA_STOCK_OBJECT )
        {
            SAL_INFO ( "emfio", "\t\t ENHMETA_STOCK_OBJECT, StockObject Enumeration: 0x" << std::hex  << nIndex );
            StockObject nStockId = static_cast<StockObject>(nIndex & 0xFF);
            switch( nStockId )
            {
                case StockObject::WHITE_BRUSH :
                {
                    maFillStyle = WinMtfFillStyle( COL_WHITE );
                    mbFillStyleSelected = true;
                }
                break;
                case StockObject::LTGRAY_BRUSH :
                {
                    maFillStyle = WinMtfFillStyle( COL_LIGHTGRAY );
                    mbFillStyleSelected = true;
                }
                break;
                case StockObject::GRAY_BRUSH :
                {
                    maFillStyle = WinMtfFillStyle( COL_GRAY );
                    mbFillStyleSelected = true;
                }
                break;
                case StockObject::DKGRAY_BRUSH :
                {
                    maFillStyle = WinMtfFillStyle( COL_GRAY7 );
                    mbFillStyleSelected = true;
                }
                break;
                case StockObject::BLACK_BRUSH :
                {
                    maFillStyle = WinMtfFillStyle( COL_BLACK );
                    mbFillStyleSelected = true;
                }
                break;
                case StockObject::NULL_BRUSH :
                {
                   maFillStyle = WinMtfFillStyle( COL_TRANSPARENT, true );
                   mbFillStyleSelected = true;
                }
                break;
                case StockObject::WHITE_PEN :
                {
                    maLineStyle = WinMtfLineStyle(COL_WHITE, PS_COSMETIC, 1);
                }
                break;
                case StockObject::BLACK_PEN :
                {
                    maLineStyle = WinMtfLineStyle(COL_BLACK, PS_COSMETIC, 1);
                }
                break;
                case StockObject::NULL_PEN :
                {
                    maLineStyle = WinMtfLineStyle( COL_TRANSPARENT, true );
                }
                break;
                default:
                break;
            }
        }
        else
        {
            nIndex &= 0xffff;       // safety check: don't allow index to be > 65535

            GDIObj *pGDIObj = nullptr;

            if ( nIndex < mvGDIObj.size() )
                pGDIObj = mvGDIObj[ nIndex ].get();

            if ( pGDIObj )
            {

                SAL_INFO ( "emfio", "\t\t Index: " << nIndex );
                if (const auto pen = dynamic_cast<WinMtfLineStyle*>(pGDIObj))
                {
                    maLineStyle = *pen;
                    SAL_INFO ( "emfio", "\t Line Style, Color: 0x" << std::hex << maLineStyle.aLineColor
                                        << ", Weight: " << maLineStyle.aLineInfo.GetWidth() );
                }
                else if (const auto brush = dynamic_cast<WinMtfFillStyle*>(
                             pGDIObj))
                {
                    maFillStyle = *brush;
                    mbFillStyleSelected = true;
                    SAL_INFO("emfio", "\t\tBrush Object, Index: " << nIndex << ", Color: " << maFillStyle.aFillColor);
                }
                else if (const auto font = dynamic_cast<WinMtfFontStyle*>(
                             pGDIObj))
                {
                    maFont = font->aFont;
                    SAL_INFO("emfio", "\t\tFont Object, Index: " << nIndex << ", Font: " << maFont.GetFamilyName() << " " << maFont.GetStyleName());
                }
                else if (const auto palette = dynamic_cast<WinMtfPalette*>(
                             pGDIObj))
                {
                    maPalette = palette->aPaletteColors;
                    SAL_INFO("emfio", "\t\tPalette Object, Index: " << nIndex << ", Number of colours: " << maPalette.aPaletteColors.size() );
                }
            }
            else
            {
                SAL_WARN("emfio", "Warning: Unable to find Object with index:" << nIndex);
            }
        }
    }

    void MtfTools::SetTextLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode )
    {
        mnTextLayoutMode = nTextLayoutMode;
    }

    void MtfTools::SetArcDirection(bool bClockWise)
    {
        SAL_INFO("emfio", "\t\t Arc direction: " << (bClockWise ? "ClockWise" : "CounterClockWise"));
        mbClockWiseArcDirection = bClockWise;
    }

    void MtfTools::SetBkMode( BackgroundMode nMode )
    {
        mnBkMode = nMode;
    }

    void MtfTools::SetBkColor( const Color& rColor )
    {
        maBkColor = rColor;
    }

    void MtfTools::SetTextColor( const Color& rColor )
    {
        maTextColor = rColor;
    }

    void MtfTools::SetTextAlign( sal_uInt32 nAlign )
    {
        mnTextAlign = nAlign;
    }

    void MtfTools::ImplResizeObjectArry( sal_uInt32 nNewEntrys )
    {
        mvGDIObj.resize(nNewEntrys);
    }

    void MtfTools::ImplDrawClippedPolyPolygon( const tools::PolyPolygon& rPolyPoly )
    {
        if ( !rPolyPoly.Count() )
            return;

        ImplSetNonPersistentLineColorTransparenz();
        if ( rPolyPoly.Count() == 1 )
        {
            if ( rPolyPoly.IsRect() )
                mpGDIMetaFile->AddAction( new MetaRectAction( rPolyPoly.GetBoundRect() ) );
            else
            {
                tools::Polygon aPoly( rPolyPoly[ 0 ] );
                sal_uInt16 nCount = aPoly.GetSize();
                if ( nCount )
                {
                    if ( aPoly[ nCount - 1 ] != aPoly[ 0 ] )
                    {
                        Point aPoint( aPoly[ 0 ] );
                        aPoly.Insert( nCount, aPoint );
                    }
                    mpGDIMetaFile->AddAction( new MetaPolygonAction( std::move(aPoly) ) );
                }
            }
        }
        else
            mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPoly ) );
    }

    void MtfTools::CreateObject( std::unique_ptr<GDIObj> pObject )
    {
        if ( pObject )
        {
            const auto pLineStyle = dynamic_cast<WinMtfLineStyle*>(pObject.get());
            const auto pFontStyle = dynamic_cast<WinMtfFontStyle*>(pObject.get());

            if ( pFontStyle )
            {
                if (pFontStyle->aFont.GetFontHeight() == 0)
                    pFontStyle->aFont.SetFontHeight(423);
                ImplMap(pFontStyle->aFont); // defaulting to 12pt
            }
            else if ( pLineStyle )
            {
                Size aSize(pLineStyle->aLineInfo.GetWidth(), 0);
                aSize = ImplMap(aSize);
                pLineStyle->aLineInfo.SetWidth(aSize.Width());
            }
        }
        std::vector<std::unique_ptr<GDIObj>>::size_type nIndex;
        for ( nIndex = 0; nIndex < mvGDIObj.size(); nIndex++ )
        {
            if ( !mvGDIObj[ nIndex ] )
                break;
        }
        if ( nIndex == mvGDIObj.size() )
            ImplResizeObjectArry( mvGDIObj.size() + 16 );

        mvGDIObj[ nIndex ] = std::move(pObject);
    }

    void MtfTools::CreateObjectIndexed( sal_uInt32 nIndex, std::unique_ptr<GDIObj> pObject )
    {
        if ( ( nIndex & ENHMETA_STOCK_OBJECT ) != 0 )
            return;

        nIndex &= 0xffff;       // safety check: do not allow index to be > 65535
        if ( pObject )
        {
            const auto pLineStyle = dynamic_cast<WinMtfLineStyle*>(pObject.get());
            const auto pFontStyle = dynamic_cast<WinMtfFontStyle*>(pObject.get());
            if ( pFontStyle )
            {
                if (pFontStyle->aFont.GetFontHeight() == 0)
                    pFontStyle->aFont.SetFontHeight(423);
                ImplMap(pFontStyle->aFont);
            }
            else if ( pLineStyle )
            {
                Size aSize(pLineStyle->aLineInfo.GetWidth(), 0);
                pLineStyle->aLineInfo.SetWidth( ImplMap(aSize).Width() );

                if ( pLineStyle->aLineInfo.GetStyle() == LineStyle::Dash )
                {
                    aSize.AdjustWidth(1 );
                    tools::Long nDashLen, nDotLen = ImplMap( aSize ).Width();
                    const bool bFail = o3tl::checked_multiply<tools::Long>(nDotLen, 3, nDashLen);
                    if (!bFail)
                    {
                        pLineStyle->aLineInfo.SetDistance( nDotLen );
                        pLineStyle->aLineInfo.SetDotLen( nDotLen );
                        pLineStyle->aLineInfo.SetDashLen( nDotLen * 3 );
                    }
                    else
                    {
                        SAL_WARN("emfio", "DotLen too long: " << nDotLen);
                    }
                }
            }
        }
        if ( nIndex >= mvGDIObj.size() )
            ImplResizeObjectArry( nIndex + 16 );

        mvGDIObj[ nIndex ] = std::move(pObject);
    }

    void MtfTools::CreateObject()
    {
        CreateObject(std::make_unique<GDIObj>());
    }

    void MtfTools::DeleteObject( sal_uInt32 nIndex )
    {
        if ( ( nIndex & ENHMETA_STOCK_OBJECT ) == 0 )
        {
            if ( nIndex < mvGDIObj.size() )
            {
                mvGDIObj[ nIndex ].reset();
            }
        }
    }

    void MtfTools::IntersectClipRect( const tools::Rectangle& rRect )
    {
        if (comphelper::IsFuzzing())
            return;
        mbClipNeedsUpdate=true;
        if ((rRect.Left()-rRect.Right()==0) && (rRect.Top()-rRect.Bottom()==0))
        {
            return; // empty rectangles cause trouble
        }
        tools::Polygon aPoly( rRect );
        const tools::PolyPolygon aPolyPolyRect( ImplMap( aPoly ) );
        maClipPath.intersectClip( aPolyPolyRect.getB2DPolyPolygon() );
    }

    void MtfTools::ExcludeClipRect( const tools::Rectangle& rRect )
    {
        if (comphelper::IsFuzzing())
            return;
        mbClipNeedsUpdate=true;
        tools::Polygon aPoly( rRect );
        const tools::PolyPolygon aPolyPolyRect( ImplMap( aPoly ) );
        maClipPath.excludeClip( aPolyPolyRect.getB2DPolyPolygon() );
    }

    void MtfTools::MoveClipRegion( const Size& rSize )
    {
        if (comphelper::IsFuzzing())
            return;
        mbClipNeedsUpdate=true;
        maClipPath.moveClipRegion( ImplMap( rSize ) );
    }

    void MtfTools::SetClipPath( const tools::PolyPolygon& rPolyPolygon, RegionMode eClippingMode, bool bIsMapped )
    {
        if (comphelper::IsFuzzing())
            return;
        mbClipNeedsUpdate = true;
        tools::PolyPolygon aPolyPolygon(rPolyPolygon);

        if (!bIsMapped)
        {
            if (!mbIsMapDevSet && (meMapMode == MappingMode::MM_ISOTROPIC || meMapMode == MappingMode::MM_ANISOTROPIC))
                aPolyPolygon = ImplScale(aPolyPolygon);
            else
                aPolyPolygon = ImplMap(aPolyPolygon);
        }
        maClipPath.setClipPath(aPolyPolygon.getB2DPolyPolygon(), eClippingMode);
    }

    void MtfTools::SetDefaultClipPath()
    {
        mbClipNeedsUpdate = true;
        maClipPath.setDefaultClipPath();
    }

    MtfTools::MtfTools( GDIMetaFile& rGDIMetaFile, SvStream& rStreamWMF)
    :   mnLatestTextAlign(90),
        mnTextAlign(TextAlignmentMode::TA_LEFT | TextAlignmentMode::TA_TOP | TextAlignmentMode::TA_NOUPDATECP),
        maLatestBkColor(ColorTransparency, 0x12345678),
        maBkColor(COL_WHITE),
        mnLatestTextLayoutMode(vcl::text::ComplexTextLayoutFlags::Default),
        mnTextLayoutMode(vcl::text::ComplexTextLayoutFlags::Default),
        mnLatestBkMode(BackgroundMode::NONE),
        mnBkMode(BackgroundMode::OPAQUE),
        meLatestRasterOp(RasterOp::Invert),
        meRasterOp(RasterOp::OverPaint),
        mnRop(),
        meGfxMode(GraphicsMode::GM_COMPATIBLE),
        meMapMode(MappingMode::MM_TEXT),
        mnDevOrgX(0),
        mnDevOrgY(0),
        mnDevWidth(1),
        mnDevHeight(1),
        mnWinOrgX(0),
        mnWinOrgY(0),
        mnWinExtX(1),
        mnWinExtY(1),
        mnPixX(100),
        mnPixY(100),
        mnMillX(1),
        mnMillY(1),
        mpGDIMetaFile(&rGDIMetaFile),
        mpInputStream(&rStreamWMF),
        mnStartPos(0),
        mnEndPos(0),
        mbNopMode(false),
        mbClockWiseArcDirection(false),
        mbFillStyleSelected(false),
        mbClipNeedsUpdate(true),
        mbComplexClip(false),
        mbIsMapWinSet(false),
        mbIsMapDevSet(false)
    {
        SvLockBytes *pLB = mpInputStream->GetLockBytes();

        if (pLB)
        {
            pLB->SetSynchronMode();
        }

        mnStartPos = mpInputStream->Tell();
        SetDevOrg(Point());

        mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::CLIPREGION ) ); // The original clipregion has to be on top
                                                                                 // of the stack so it can always be restored
                                                                                 // this is necessary to be able to support
                                                                                 // SetClipRgn( NULL ) and similar ClipRgn actions (SJ)

        maFont.SetFamilyName( u"Arial"_ustr );                                         // sj: #i57205#, we do have some scaling problems if using
        maFont.SetCharSet( RTL_TEXTENCODING_MS_1252 );                           // the default font then most times a x11 font is used, we
        maFont.SetFontHeight( 423 );                                      // will prevent this defining a font

        maLatestLineStyle.aLineColor = Color( 0x12, 0x34, 0x56 );
        maLatestFillStyle.aFillColor = Color( 0x12, 0x34, 0x56 );

        mnRop = WMFRasterOp::Black;
        meRasterOp = RasterOp::OverPaint;
        mpGDIMetaFile->AddAction( new MetaRasterOpAction( RasterOp::OverPaint ) );
    }

    MtfTools::~MtfTools()
    {
        mpGDIMetaFile->AddAction( new MetaPopAction() );
        mpGDIMetaFile->SetPrefMapMode(MapMode(MapUnit::Map100thMM));
        if ( mrclFrame.IsEmpty() )
            mpGDIMetaFile->SetPrefSize( Size( mnDevWidth, mnDevHeight ) );
        else
            mpGDIMetaFile->SetPrefSize( mrclFrame.GetSize() );
    }

    void MtfTools::UpdateClipRegion()
    {
        if (!mbClipNeedsUpdate)
            return;

        mbClipNeedsUpdate = false;
        mbComplexClip = false;

        mpGDIMetaFile->AddAction( new MetaPopAction() );                    // taking the original clipregion
        mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::CLIPREGION ) );

        // skip for 'no clipping at all' case
        if( maClipPath.isEmpty() )
            return;

        const basegfx::B2DPolyPolygon& rClipPoly( maClipPath.getClipPath() );

        mbComplexClip = rClipPoly.count() > 1
            || !basegfx::utils::isRectangle(rClipPoly);

        // This makes cases like tdf#45820 work in reasonable time.
        if (mbComplexClip)
        {
            mpGDIMetaFile->AddAction(
                new MetaISectRegionClipRegionAction(
                    vcl::Region(rClipPoly)));
            mbComplexClip = false;
        }
        else
        {
            mpGDIMetaFile->AddAction(
                new MetaISectRectClipRegionAction(
                    vcl::unotools::rectangleFromB2DRectangle(
                        rClipPoly.getB2DRange())));
        }
    }

    void MtfTools::ImplSetNonPersistentLineColorTransparenz()
    {
        WinMtfLineStyle aTransparentLine( COL_TRANSPARENT, true );
        if ( ! ( maLatestLineStyle == aTransparentLine ) )
        {
            maLatestLineStyle = aTransparentLine;
            mpGDIMetaFile->AddAction( new MetaLineColorAction( aTransparentLine.aLineColor, !aTransparentLine.bTransparent ) );
        }
    }

    void MtfTools::ResetInternalState()
    {
        maLatestBkColor = Color(0x12, 0x34, 0x56);
        mnLatestBkMode = BackgroundMode::NONE;

        maLatestFillStyle.aFillColor = Color(0x12, 0x34, 0x56);
        maLatestLineStyle.aLineColor = Color(0x12, 0x34, 0x56);

        meLatestRasterOp = RasterOp::Invert;

        mnLatestTextAlign = 90;
        mnLatestTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default;
    }

    void MtfTools::UpdateLineStyle()
    {
        if (!( maLatestLineStyle == maLineStyle ) )
        {
            maLatestLineStyle = maLineStyle;
            mpGDIMetaFile->AddAction( new MetaLineColorAction( maLineStyle.aLineColor, !maLineStyle.bTransparent ) );
        }
    }

    void MtfTools::UpdateFillStyle()
    {
        if ( !mbFillStyleSelected )     // SJ: #i57205# taking care of bkcolor if no brush is selected
            maFillStyle = WinMtfFillStyle( maBkColor, mnBkMode == BackgroundMode::Transparent );
        if (!( maLatestFillStyle == maFillStyle ) )
        {
            maLatestFillStyle = maFillStyle;
            if (maFillStyle.aType == WinMtfFillStyleType::Solid)
                mpGDIMetaFile->AddAction( new MetaFillColorAction( maFillStyle.aFillColor, !maFillStyle.bTransparent ) );
        }
    }

    WMFRasterOp MtfTools::SetRasterOp( WMFRasterOp nRasterOp )
    {
        WMFRasterOp nRetROP = mnRop;
        if ( nRasterOp != mnRop )
        {
            mnRop = nRasterOp;

            if ( mbNopMode && ( nRasterOp != WMFRasterOp::Nop ) )
            {   // changing modes from WMFRasterOp::Nop so set pen and brush
                maFillStyle = maNopFillStyle;
                maLineStyle = maNopLineStyle;
                mbNopMode = false;
            }
            switch( nRasterOp )
            {
                case WMFRasterOp::Not:
                    meRasterOp = RasterOp::Invert;
                break;

                case WMFRasterOp::XorPen:
                    meRasterOp = RasterOp::Xor;
                break;

                case WMFRasterOp::Nop:
                {
                    meRasterOp = RasterOp::OverPaint;
                    if( !mbNopMode )
                    {
                        maNopFillStyle = maFillStyle;
                        maNopLineStyle = maLineStyle;
                        maFillStyle = WinMtfFillStyle( COL_TRANSPARENT, true );
                        maLineStyle = WinMtfLineStyle( COL_TRANSPARENT, true );
                        mbNopMode = true;
                    }
                }
                break;

                default:
                    meRasterOp = RasterOp::OverPaint;
                break;
            }
        }
        if ( nRetROP != nRasterOp )
            mpGDIMetaFile->AddAction( new MetaRasterOpAction( meRasterOp ) );
        return nRetROP;
    };

    void MtfTools::StrokeAndFillPath( bool bStroke, bool bFill )
    {
        if ( !maPathObj.Count() )
            return;

        UpdateClipRegion();
        UpdateLineStyle();
        UpdateFillStyle();
        if ( bFill )
        {
            if ( !bStroke )
            {
                mpGDIMetaFile->AddAction( new MetaPushAction( vcl::PushFlags::LINECOLOR ) );
                mpGDIMetaFile->AddAction( new MetaLineColorAction( Color(), false ) );
            }
            if ( maPathObj.Count() == 1 )
                mpGDIMetaFile->AddAction( new MetaPolygonAction( maPathObj.GetObject( 0 ) ) );
            else
                mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( maPathObj ) );

            if ( !bStroke )
                mpGDIMetaFile->AddAction( new MetaPopAction() );
        }
        // tdf#142014 By default the stroke is made with hairline. If width is bigger, we need to use PolyLineAction
        if ( bStroke )
        {
            // bFill is drawing hairstyle line. So we need to draw it only when the width is different than 0
            if ( !bFill || maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
            {
                sal_uInt16 i, nCount = maPathObj.Count();
                for ( i = 0; i < nCount; i++ )
                    mpGDIMetaFile->AddAction( new MetaPolyLineAction( maPathObj[ i ], maLineStyle.aLineInfo ) );
            }
        }
        ClearPath();
    }

    void MtfTools::DrawPixel( const Point& rSource, const Color& rColor )
    {
        mpGDIMetaFile->AddAction( new MetaPixelAction( ImplMap( rSource), rColor ) );
    }

    void MtfTools::MoveTo( const Point& rPoint, bool bRecordPath )
    {
        Point aDest( ImplMap( rPoint ) );
        if ( bRecordPath )
        {
            // fdo#57353 create new subpath for subsequent moves
            if ( maPathObj.Count() )
                if ( maPathObj[ maPathObj.Count() - 1 ].GetSize() )
                    maPathObj.Insert( tools::Polygon() );
            maPathObj.AddPoint( aDest );
        }
        maActPos = aDest;
    }

    void MtfTools::LineTo( const Point& rPoint, bool bRecordPath )
    {
        UpdateClipRegion();
        Point aDest( ImplMap( rPoint ) );
        if ( bRecordPath )
            maPathObj.AddPoint( aDest );
        else
        {
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaLineAction( maActPos, aDest, maLineStyle.aLineInfo ) );
        }
        maActPos = aDest;
    }

    void MtfTools::DrawRectWithBGColor(const tools::Rectangle& rRect)
    {
        WinMtfFillStyle aFillStyleBackup = maFillStyle;
        bool            bTransparentBackup = maLineStyle.bTransparent;
        BackgroundMode  mnBkModeBackup = mnBkMode;

        const tools::Polygon aPoly( rRect );
        maLineStyle.bTransparent = true;
        maFillStyle = maBkColor;
        mnBkMode = BackgroundMode::OPAQUE;
        ImplSetNonPersistentLineColorTransparenz();
        DrawPolygon(aPoly, false);
        mnBkMode = mnBkModeBackup; // The rectangle needs to be always drawned even if mode is transparent
        maFillStyle = std::move(aFillStyleBackup);
        maLineStyle.bTransparent = bTransparentBackup;
    }

    void MtfTools::DrawRect( const tools::Rectangle& rRect, bool bEdge )
    {
        UpdateClipRegion();
        UpdateFillStyle();

        if ( mbComplexClip )
        {
            tools::Polygon aPoly( ImplMap( rRect ) );
            tools::PolyPolygon aPolyPolyRect( aPoly );
            tools::PolyPolygon aDest;
            tools::PolyPolygon(maClipPath.getClipPath()).GetIntersection( aPolyPolyRect, aDest );
            ImplDrawClippedPolyPolygon( aDest );
        }
        else
        {
            if ( bEdge )
            {
                if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
                {
                    ImplSetNonPersistentLineColorTransparenz();
                    mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) );
                    UpdateLineStyle();
                    mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( ImplMap( rRect ) ),maLineStyle.aLineInfo ) );
                }
                else
                {
                    UpdateLineStyle();
                    mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) );
                }
            }
            else
            {
                ImplSetNonPersistentLineColorTransparenz();
                mpGDIMetaFile->AddAction( new MetaRectAction( ImplMap( rRect ) ) );
            }
        }
    }

    void MtfTools::DrawRoundRect( const tools::Rectangle& rRect, const Size& rSize )
    {
        UpdateClipRegion();
        UpdateLineStyle();
        UpdateFillStyle();
        mpGDIMetaFile->AddAction( new MetaRoundRectAction( ImplMap( rRect ), std::abs( ImplMap( rSize ).Width() ), std::abs( ImplMap( rSize ).Height() ) ) );
        // tdf#142139 Wrong line width during WMF import
        if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
        {
            tools::Polygon aRoundRectPoly( rRect, rSize.Width(), rSize.Height() );
            mpGDIMetaFile->AddAction( new MetaPolyLineAction( ImplMap( aRoundRectPoly ), maLineStyle.aLineInfo ) );
        }
    }

    void MtfTools::DrawEllipse( const tools::Rectangle& rRect )
    {
        UpdateClipRegion();
        UpdateFillStyle();

        if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
        {
            Point aCenter( ImplMap( rRect.Center() ) );
            Size  aRad( ImplMap( Size( rRect.GetWidth() / 2, rRect.GetHeight() / 2 ) ) );

            ImplSetNonPersistentLineColorTransparenz();
            mpGDIMetaFile->AddAction( new MetaEllipseAction( ImplMap( rRect ) ) );
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( std::move(aCenter), aRad.Width(), aRad.Height() ), maLineStyle.aLineInfo ) );
        }
        else
        {
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaEllipseAction( ImplMap( rRect ) ) );
        }
    }

    void MtfTools::DrawArc( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd, bool bTo )
    {
        UpdateClipRegion();
        UpdateLineStyle();
        UpdateFillStyle();

        tools::Rectangle   aRect( ImplMap( rRect ) );
        Point       aStart( ImplMap( rStart ) );
        Point       aEnd( ImplMap( rEnd ) );

        if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
        {
            if ( aStart == aEnd )
            {   // SJ: #i53768# if start & end is identical, then we have to draw a full ellipse
                Point aCenter( aRect.Center() );
                Size  aRad( aRect.GetWidth() / 2, aRect.GetHeight() / 2 );

                mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( std::move(aCenter), aRad.Width(), aRad.Height() ), maLineStyle.aLineInfo ) );
            }
            else
                mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Arc ), maLineStyle.aLineInfo ) );
        }
        else
            mpGDIMetaFile->AddAction( new MetaArcAction( aRect, aStart, aEnd ) );

        if ( bTo )
            maActPos = aEnd;
    }

    void MtfTools::DrawPie( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd )
    {
        UpdateClipRegion();
        UpdateFillStyle();

        tools::Rectangle   aRect( ImplMap( rRect ) );
        Point       aStart( ImplMap( rStart ) );
        Point       aEnd( ImplMap( rEnd ) );

        if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
        {
            ImplSetNonPersistentLineColorTransparenz();
            mpGDIMetaFile->AddAction( new MetaPieAction( aRect, aStart, aEnd ) );
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Pie ), maLineStyle.aLineInfo ) );
        }
        else
        {
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaPieAction( aRect, aStart, aEnd ) );
        }
    }

    void MtfTools::DrawChord( const tools::Rectangle& rRect, const Point& rStart, const Point& rEnd )
    {
        UpdateClipRegion();
        UpdateFillStyle();

        tools::Rectangle   aRect( ImplMap( rRect ) );
        Point       aStart( ImplMap( rStart ) );
        Point       aEnd( ImplMap( rEnd ) );

        if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
        {
            ImplSetNonPersistentLineColorTransparenz();
            mpGDIMetaFile->AddAction( new MetaChordAction( aRect, aStart, aEnd ) );
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaPolyLineAction( tools::Polygon( aRect, aStart, aEnd, PolyStyle::Chord ), maLineStyle.aLineInfo ) );
        }
        else
        {
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaChordAction( aRect, aStart, aEnd ) );
        }
    }

    void MtfTools::DrawPolygon( tools::Polygon rPolygon, bool bRecordPath )
    {
        UpdateClipRegion();
        ImplMap( rPolygon );
        if ( bRecordPath )
            maPathObj.AddPolygon( rPolygon );
        else
        {
            UpdateFillStyle();

            if ( mbComplexClip )
            {
                tools::PolyPolygon aPolyPoly( rPolygon );
                auto tmp = maClipPath.getClip();
                tmp.intersectPolyPolygon(aPolyPoly.getB2DPolyPolygon());
                tools::PolyPolygon aDest(tmp.getClipPoly());
                ImplDrawClippedPolyPolygon( aDest );
            }
            else
            {
                if ( maLineStyle.aLineInfo.GetWidth() || ( maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash ) )
                {
                    sal_uInt16 nCount = rPolygon.GetSize();
                    if ( nCount )
                    {
                        if ( rPolygon[ nCount - 1 ] != rPolygon[ 0 ] )
                        {
                            Point aPoint( rPolygon[ 0 ] );
                            rPolygon.Insert( nCount, aPoint );
                        }
                    }
                    ImplSetNonPersistentLineColorTransparenz();
                    mpGDIMetaFile->AddAction( new MetaPolygonAction( rPolygon ) );
                    UpdateLineStyle();
                    mpGDIMetaFile->AddAction( new MetaPolyLineAction( std::move(rPolygon), maLineStyle.aLineInfo ) );
                }
                else
                {
                    UpdateLineStyle();

                    if (maLatestFillStyle.aType != WinMtfFillStyleType::Pattern)
                        mpGDIMetaFile->AddAction( new MetaPolygonAction( std::move(rPolygon) ) );
                    else {
                        SvtGraphicFill aFill( tools::PolyPolygon( rPolygon ),
                                              Color(),
                                              0.0,
                                              SvtGraphicFill::fillNonZero,
                                              SvtGraphicFill::fillTexture,
                                              SvtGraphicFill::Transform(),
                                              true,
                                              SvtGraphicFill::hatchSingle,
                                              Color(),
                                              SvtGraphicFill::GradientType::Linear,
                                              Color(),
                                              Color(),
                                              0,
                                              Graphic(maLatestFillStyle.aBmp));

                        SvMemoryStream  aMemStm;

                        WriteSvtGraphicFill( aMemStm, aFill );

                        mpGDIMetaFile->AddAction( new MetaCommentAction( "XPATHFILL_SEQ_BEGIN"_ostr, 0,
                                                                static_cast<const sal_uInt8*>(aMemStm.GetData()),
                                                                aMemStm.TellEnd() ) );
                        mpGDIMetaFile->AddAction( new MetaCommentAction( "XPATHFILL_SEQ_END"_ostr ) );
                    }

                }
            }
        }
    }

    void MtfTools::DrawPolyPolygon( tools::PolyPolygon& rPolyPolygon, bool bRecordPath )
    {
        UpdateClipRegion();

        ImplMap( rPolyPolygon );

        if ( bRecordPath )
            maPathObj.AddPolyPolygon( rPolyPolygon );
        else
        {
            UpdateFillStyle();

            if ( mbComplexClip )
            {
                tools::PolyPolygon aDest;
                tools::PolyPolygon(maClipPath.getClipPath()).GetIntersection( rPolyPolygon, aDest );
                ImplDrawClippedPolyPolygon( aDest );
            }
            else
            {
                UpdateLineStyle();
                mpGDIMetaFile->AddAction( new MetaPolyPolygonAction( rPolyPolygon ) );
                if (maLineStyle.aLineInfo.GetWidth() > 0 || maLineStyle.aLineInfo.GetStyle() == LineStyle::Dash)
                {
                    for (sal_uInt16 nPoly = 0; nPoly < rPolyPolygon.Count(); ++nPoly)
                    {
                        mpGDIMetaFile->AddAction(new MetaPolyLineAction(rPolyPolygon[nPoly], maLineStyle.aLineInfo));
                    }
                }
            }
        }
    }

    void MtfTools::DrawPolyLine( tools::Polygon rPolygon, bool bTo, bool bRecordPath )
    {
        UpdateClipRegion();

        sal_uInt16 nPoints = rPolygon.GetSize();
        if (nPoints < 1)
            return;

        ImplMap( rPolygon );
        if ( bTo )
        {
            rPolygon[ 0 ] = maActPos;
            maActPos = rPolygon[ rPolygon.GetSize() - 1 ];
        }
        if ( bRecordPath )
            maPathObj.AddPolyLine( rPolygon );
        else
        {
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaPolyLineAction( std::move(rPolygon), maLineStyle.aLineInfo ) );
        }
    }

    static bool AllowDim(tools::Long nDim)
    {
        static bool bFuzzing = comphelper::IsFuzzing();
        if (bFuzzing)
        {
            if (nDim > 0x20000000 || nDim < -0x20000000)
            {
                SAL_WARN("vcl", "skipping huge dimension: " << nDim);
                return false;
            }
        }
        return true;
    }

    static bool AllowPoint(const Point& rPoint)
    {
        return AllowDim(rPoint.X()) && AllowDim(rPoint.Y());
    }

    void MtfTools::DrawPolyBezier( tools::Polygon rPolygon, bool bTo, bool bRecordPath )
    {
        sal_uInt16 nPoints = rPolygon.GetSize();
        if ( ( nPoints < 4 ) || ( ( ( nPoints - 4 ) % 3 ) != 0 ) )
        {
            SAL_WARN("emfio",
                "EMF file error: Number of Bezier points is not set of three");
            return;
        }
        UpdateClipRegion();
        ImplMap( rPolygon );
        if ( bTo )
        {
            rPolygon[ 0 ] = maActPos;
            maActPos = rPolygon[ nPoints - 1 ];
        }
        sal_uInt16 i;
        for ( i = 0; ( i + 2 ) < nPoints; )
        {
            rPolygon.SetFlags( i++, PolyFlags::Normal );
            rPolygon.SetFlags( i++, PolyFlags::Control );
            rPolygon.SetFlags( i++, PolyFlags::Control );
        }
        if ( bRecordPath )
            maPathObj.AddPolyLine( rPolygon );
        else
        {
            UpdateLineStyle();
            mpGDIMetaFile->AddAction( new MetaPolyLineAction( std::move(rPolygon), maLineStyle.aLineInfo ) );
        }
    }

    void MtfTools::DrawText(Point& rPosition, OUString const & rText, KernArray* pDXArry, tools::Long* pDYArry, const float fXScale, const float fYScale, bool bRecordPath, GraphicsMode nGfxMode)
    {
        UpdateClipRegion();
        rPosition = ImplMap( rPosition );
        GraphicsMode nOldGfxMode = GetGfxMode();
        SetGfxMode( GraphicsMode::GM_COMPATIBLE );

        if (pDXArry)
        {
            sal_Int64 nSumX = 0, nSumY = 0;
            for (sal_Int32 i = 0; i < rText.getLength(); i++ )
            {
                nSumX += (*pDXArry)[i];

                // #i121382# Map DXArray using WorldTransform
                const Size aSizeX(ImplMap(Size(nSumX, 0)));
                const basegfx::B2DVector aVectorX(aSizeX.Width(), aSizeX.Height());
                (*pDXArry)[i] = aVectorX.getLength() * (nSumX >= 0 ? 1 : -1);

                if (pDYArry)
                {
                    nSumY += pDYArry[i];

                    const Size aSizeY(ImplMap(Size(0, nSumY)));
                    const basegfx::B2DVector aVectorY(aSizeY.Width(), aSizeY.Height());
                    // Reverse Y
                    pDYArry[i] = basegfx::fround<tools::Long>(aVectorY.getLength());
                    pDYArry[i] *= (nSumY >= 0 ? -1 : 1);
                }
            }
        }
        if ( mnLatestTextLayoutMode != mnTextLayoutMode )
        {
            mnLatestTextLayoutMode = mnTextLayoutMode;
            mpGDIMetaFile->AddAction( new MetaLayoutModeAction( mnTextLayoutMode ) );
        }
        SetGfxMode(nGfxMode);
        TextAlign eTextAlign;
        if (mnTextAlign & TA_BASELINE)
            eTextAlign = ALIGN_BASELINE;
        else if (mnTextAlign & TA_BOTTOM)
            eTextAlign = ALIGN_BOTTOM;
        else
            eTextAlign = ALIGN_TOP;
        bool bChangeFont = false;
        if ( mnLatestTextAlign != mnTextAlign )
        {
            bChangeFont = true;

            if ((mnLatestTextAlign & TA_RTLREADING) != (mnTextAlign & TA_RTLREADING))
            {
                auto nFlags = vcl::text::ComplexTextLayoutFlags::Default;
                if (mnTextAlign & TA_RTLREADING)
                {
                    nFlags = vcl::text::ComplexTextLayoutFlags::BiDiRtl
                             | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
                }

                mpGDIMetaFile->AddAction(new MetaLayoutModeAction(nFlags));
            }

            mnLatestTextAlign = mnTextAlign;
            mpGDIMetaFile->AddAction( new MetaTextAlignAction( eTextAlign ) );
        }
        if ( maLatestTextColor != maTextColor )
        {
            bChangeFont = true;
            maLatestTextColor = maTextColor;
            mpGDIMetaFile->AddAction( new MetaTextColorAction( maTextColor ) );
        }
        bool bChangeFillColor = false;
        if ( maLatestBkColor != maBkColor )
        {
            bChangeFillColor = true;
            maLatestBkColor = maBkColor;
        }
        if ( mnLatestBkMode != mnBkMode )
        {
            bChangeFillColor = true;
            mnLatestBkMode = mnBkMode;
        }
        if ( bChangeFillColor )
        {
            bChangeFont = true;
            mpGDIMetaFile->AddAction( new MetaTextFillColorAction( maFont.GetFillColor(), !maFont.IsTransparent() ) );
        }
        // Create a local copy of the current font to apply transient modifications
        // (such as color, alignment, and custom scaling) before recording it to the metafile.
        vcl::Font aTmp( maFont );
        aTmp.SetColor( maTextColor );

        if( mnBkMode == BackgroundMode::Transparent )
        {
            aTmp.SetFillColor( COL_TRANSPARENT );
            aTmp.SetTransparent( true );
        }
        else
        {
            aTmp.SetFillColor( maBkColor );
            aTmp.SetTransparent( false );
        }

        aTmp.SetAlignment( eTextAlign );

        if ( nGfxMode == GraphicsMode::GM_ADVANCED )
        {
            // check whether there is a font rotation applied via transformation
            Point aP1( ImplMap( Point() ) );
            Point aP2( ImplMap( Point( 0, 100 ) ) );
            aP2.setX(o3tl::saturating_sub(aP2.X(), aP1.X()));
            aP2.setY(o3tl::saturating_sub(aP2.Y(), aP1.Y()));
            double fX = aP2.X();
            double fY = aP2.Y();
            if ( fX )
            {
                double fOrientation = basegfx::rad2deg(acos(fX / std::hypot(fX, fY)));
                if ( fY > 0 )
                    fOrientation = 360 - fOrientation;
                fOrientation += 90;
                fOrientation *= 10;
                aTmp.SetOrientation( aTmp.GetOrientation() + Degree10( static_cast<sal_Int16>(fOrientation) ) );
            }
        }
        else if (nGfxMode == GraphicsMode::GM_COMPATIBLE)
        {
            if (fXScale != 0.0)
            {
                // As we changing only font width, we are skippin if scales have the same values
                const bool bNeedsWidthScale = (std::fabs(fYScale) != std::fabs(fXScale));
                if (bNeedsWidthScale)
                {
                    Size aFontSize = aTmp.GetFontSize();
                    const float fTestWidthScale = std::fabs(fYScale / fXScale);

                    // If Width is 0, the font is scaled proportionally based on Height.
                    if (aFontSize.Width() == 0)
                        aFontSize.setWidth(basegfx::fround<tools::Long>(aFontSize.Height() * fTestWidthScale));
                    else
                        aFontSize.setWidth(basegfx::fround<tools::Long>(aFontSize.Width() * fTestWidthScale));
                    aTmp.SetFontSize(aFontSize);
                    bChangeFont = true;
                }
            }

            if ((fYScale < 0.0) && (fXScale < 0.0))
            {
                // Both scales negative = 180 degree rotation. vcl::Font handles this perfectly.
                aTmp.SetOrientation(aTmp.GetOrientation() + Degree10(1800));
            }
            else if ((fYScale < 0.0) || (fXScale < 0.0))
            {
                // Single-axis mirroring.
                // In GM_COMPATIBLE, text glyphs are NOT physically mirrored.
                // However, the flipped coordinate system reverses the rotation direction.
                // Inverting the angle (360 degrees - current angle) fixes the text direction.
                aTmp.SetOrientation(Degree10(3600) - aTmp.GetOrientation());

                // TODO: True single-axis glyph mirroring would require a MapMode transform here
            }
        }

        if( mnTextAlign & ( TA_UPDATECP | TA_RIGHT_CENTER ) )
        {
            // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
            SolarMutexGuard aGuard;
            ScopedVclPtrInstance< VirtualDevice > pVDev;
            sal_Int32 nTextWidth;
            Point aActPosDelta;
            pVDev->SetMapMode( MapMode( MapUnit::Map100thMM ) );
            pVDev->SetFont( maFont );
            const sal_uInt32 nLen = pDXArry ? rText.getLength() : 0;
            if (nLen)
            {
                nTextWidth = pVDev->GetTextWidth( OUString(rText[ nLen - 1 ]) );
                if( nLen > 1 )
                    nTextWidth += (*pDXArry)[ nLen - 2 ];
                // tdf#39894: We should consider the distance to next character cell origin
                aActPosDelta.setX( (*pDXArry)[ nLen - 1 ] );
                if ( pDYArry )
                {
                    aActPosDelta.setY( pDYArry[ nLen - 1 ] );
                }
            }
            else
            {
                nTextWidth = pVDev->GetTextWidth( rText );
                aActPosDelta.setX( nTextWidth );
            }

            if( mnTextAlign & TA_UPDATECP )
                rPosition = maActPos;

            if (mnTextAlign & TA_RIGHT_CENTER)
            {
                Point aDisplacement(((mnTextAlign & TA_RIGHT_CENTER) == TA_CENTER) ? nTextWidth >> 1: nTextWidth, 0);
                Point().RotateAround(aDisplacement, maFont.GetOrientation());
                rPosition -= aDisplacement;
            }

            if( mnTextAlign & TA_UPDATECP )
            {
                Point().RotateAround(aActPosDelta, maFont.GetOrientation());
                maActPos = rPosition + aActPosDelta;
            }
        }

        if(bChangeFont || (maLatestFont != aTmp))
        {
            maLatestFont = aTmp;
            rtl::Reference<MetaFontAction> aNewMetaFontAction(new MetaFontAction(aTmp));

            // tdf#127471 end evtl active MetaFontAction scale corrector detector/collector
            maScaledFontHelper.endCurrentMetaFontAction();

            // !bRecordPath: else no MetaTextArrayAction will be created
            // nullptr != pDXArry: detection only possible when text size is given
            // rText.getLength(): no useful check without text
            if(!bRecordPath && nullptr != pDXArry && 0 != rText.getLength())
            {
                maScaledFontHelper.newCurrentMetaFontAction(aNewMetaFontAction);
            }

            mpGDIMetaFile->AddAction( aNewMetaFontAction );
            mpGDIMetaFile->AddAction( new MetaTextAlignAction( aTmp.GetAlignment() ) );
            mpGDIMetaFile->AddAction( new MetaTextColorAction( aTmp.GetColor() ) );
            mpGDIMetaFile->AddAction( new MetaTextFillColorAction( aTmp.GetFillColor(), !aTmp.IsTransparent() ) );
        }

        if ( bRecordPath )
        {
            // TODO
        }
        else
        {
            if ( pDXArry && pDYArry )
            {
                for (sal_Int32 i = 0; i < rText.getLength(); ++i)
                {
                    Point aCharDisplacement( i ? (*pDXArry)[i-1] : 0, i ? pDYArry[i-1] : 0 );
                    if (AllowPoint(aCharDisplacement))
                    {
                        Point().RotateAround(aCharDisplacement, maFont.GetOrientation());
                        mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition + aCharDisplacement, OUString( rText[i] ), KernArraySpan(), {}, 0, 1 ) );
                    }
                }
            }
            else
            {
                /* because text without dx array is badly scaled, we
                   will create such an array if necessary */
                KernArraySpan pDX;
                KernArray aMyDXArray;
                if (pDXArry)
                {
                    pDX = *pDXArry;
                    // only useful when we have an imported DXArray
                    if(!rText.isEmpty())
                    {
                        maScaledFontHelper.evaluateAlternativeFontScale(
                            rText,
                            (*pDXArry)[rText.getLength() - 1] // extract imported TextLength
                        );
                    }
                }
                else
                {
                    // #i117968# VirtualDevice is not thread safe, but filter is used in multithreading
                    SolarMutexGuard aGuard;
                    ScopedVclPtrInstance< VirtualDevice > pVDev;
                    pVDev->SetMapMode(MapMode(MapUnit::Map100thMM));
                    pVDev->SetFont( maLatestFont );
                    pVDev->GetTextArray( rText, &aMyDXArray, 0, rText.getLength());
                    pDX = aMyDXArray;
                }
                mpGDIMetaFile->AddAction( new MetaTextArrayAction( rPosition, rText, pDX, {}, 0, rText.getLength() ) );
            }
        }
        SetGfxMode( nOldGfxMode );
    }

    void MtfTools::ImplDrawBitmap( const Point& rPos, const Size& rSize, const Bitmap& rBitmap )
    {
        Bitmap aBmp( rBitmap );
        if ( mbComplexClip )
        {
            vcl::bitmap::DrawAndClipBitmap(rPos, rSize, rBitmap, aBmp, maClipPath.getClipPath());
        }

        if ( aBmp.HasAlpha() )
            mpGDIMetaFile->AddAction( new MetaBmpExScaleAction( rPos, rSize, aBmp ) );
        else
            mpGDIMetaFile->AddAction( new MetaBmpScaleAction( rPos, rSize, aBmp ) );
    }

    void MtfTools::ResolveBitmapActions( std::vector<BSaveStruct>& rSaveList )
    {
        UpdateClipRegion();

        size_t nObjects     = rSaveList.size();
        size_t nObjectsLeft = nObjects;

        while ( nObjectsLeft )
        {
            size_t          i;
            size_t          nObjectsOfSameSize = 0;
            size_t          nObjectStartIndex = nObjects - nObjectsLeft;

            BSaveStruct*    pSave = &rSaveList[nObjectStartIndex];
            tools::Rectangle       aRect( pSave->aOutRect );

            for ( i = nObjectStartIndex; i < nObjects; )
            {
                nObjectsOfSameSize++;
                if ( ++i < nObjects )
                {
                    pSave = &rSaveList[i];
                    if ( pSave->aOutRect != aRect )
                        break;
                }
            }
            Point   aPos( ImplMap( aRect.TopLeft() ) );
            Size    aSize( ImplMap( aRect.GetSize() ) );

            for ( i = nObjectStartIndex; i < ( nObjectStartIndex + nObjectsOfSameSize ); i++ )
            {
                pSave = &rSaveList[i];

                sal_uInt32  nWinRop = pSave->nWinRop;
                sal_uInt8   nRasterOperation = static_cast<sal_uInt8>( nWinRop >> 16 );

                sal_uInt32  nUsed =  0;
                if ( ( nRasterOperation & 0xf )  != ( nRasterOperation >> 4 ) )
                    nUsed |= 1;     // pattern is used
                if ( ( nRasterOperation & 0x33 ) != ( ( nRasterOperation & 0xcc ) >> 2 ) )
                    nUsed |= 2;     // source is used
                if ( ( nRasterOperation & 0xaa ) != ( ( nRasterOperation & 0x55 ) << 1 ) )
                    nUsed |= 4;     // destination is used

                if ( (nUsed & 1) && (( nUsed & 2 ) == 0) && nWinRop != PATINVERT )
                {   // patterns aren't well supported yet
                    WMFRasterOp nOldRop = SetRasterOp( WMFRasterOp::NONE );  // in this case nRasterOperation is either 0 or 0xff
                    UpdateFillStyle();
                    DrawRect( aRect, false );
                    SetRasterOp( nOldRop );
                }
                else
                {
                    bool bDrawn = false;

                    if ( i == nObjectStartIndex )   // optimizing, sometimes it is possible to create just one transparent bitmap
                    {
                        if ( nObjectsOfSameSize == 2 )
                        {
                            BSaveStruct* pSave2 = &rSaveList[i + 1];
                            if ( ( pSave->aBmp.GetPrefSize() == pSave2->aBmp.GetPrefSize() ) &&
                                 ( pSave->aBmp.GetPrefMapMode() == pSave2->aBmp.GetPrefMapMode() ) )
                            {
                                // TODO: Strictly speaking, we should
                                // check whether mask is monochrome, and
                                // whether image is black (upper branch)
                                // or white (lower branch). Otherwise, the
                                // effect is not the same as a masked
                                // bitmap.
                                if ( ( nWinRop == SRCPAINT ) && ( pSave2->nWinRop == SRCAND ) )
                                {
                                    Bitmap aMask( pSave->aBmp.CreateColorBitmap() );
                                    aMask.Invert();
                                    Bitmap aBmpEx( pSave2->aBmp.CreateColorBitmap(), aMask );
                                    ImplDrawBitmap( aPos, aSize, aBmpEx );
                                    bDrawn = true;
                                    i++;
                                }
                                // #i20085# This is just the other way
                                // around as above. Only difference: mask
                                // is inverted
                                else if ( ( nWinRop == SRCAND ) && ( pSave2->nWinRop == SRCPAINT ) )
                                {
                                    const Bitmap aMask( pSave->aBmp.CreateColorBitmap() );
                                    Bitmap aBmpEx( pSave2->aBmp.CreateColorBitmap(), aMask );
                                    ImplDrawBitmap( aPos, aSize, aBmpEx );
                                    bDrawn = true;
                                    i++;
                                }
                                // tdf#90539
                                else if ( ( nWinRop == SRCAND ) && ( pSave2->nWinRop == SRCINVERT ) )
                                {
                                    const Bitmap aMask( pSave->aBmp.CreateColorBitmap() );
                                    Bitmap aBmpEx( pSave2->aBmp.CreateColorBitmap(), aMask );
                                    ImplDrawBitmap( aPos, aSize, aBmpEx );
                                    bDrawn = true;
                                    i++;
                                }
                            }
                        }
                    }

                    if ( !bDrawn )
                    {
                        Push();
                        WMFRasterOp nOldRop = SetRasterOp( WMFRasterOp::CopyPen );
                        Bitmap      aBitmap( pSave->aBmp.CreateColorBitmap() );
                        sal_uInt32  nOperation = ( nRasterOperation & 0xf );
                        switch( nOperation )
                        {
                            case 0x1 :
                            case 0xe :
                            {
                                if(pSave->aBmp.HasAlpha())
                                {
                                    ImplDrawBitmap( aPos, aSize, pSave->aBmp );
                                }
                                else
                                {
                                    SetRasterOp( WMFRasterOp::XorPen );
                                    ImplDrawBitmap( aPos, aSize, aBitmap );
                                    SetRasterOp( WMFRasterOp::CopyPen );
                                    Bitmap  aMask( aBitmap );
                                    aMask.Invert();
                                    Bitmap aBmpEx( aBitmap, aMask );
                                    ImplDrawBitmap( aPos, aSize, aBmpEx );
                                    if ( nOperation == 0x1 )
                                    {
                                        SetRasterOp( WMFRasterOp::Not );
                                        DrawRect( aRect, false );
                                    }
                                }
                            }
                            break;
                            case 0x7 :
                            case 0x8 :
                            {
                                Bitmap  aMask( aBitmap );
                                if ( ( nUsed & 1 ) && ( nRasterOperation & 0xb0 ) == 0xb0 )     // pattern used
                                {
                                    aBitmap.Convert( BmpConversion::N24Bit );
                                    aBitmap.Erase( maFillStyle.aFillColor );
                                }
                                Bitmap aBmpEx( aBitmap, aMask );
                                ImplDrawBitmap( aPos, aSize, aBmpEx );
                                if ( nOperation == 0x7 )
                                {
                                    SetRasterOp( WMFRasterOp::Not );
                                    DrawRect( aRect, false );
                                }
                            }
                            break;

                            case 0x4 :
                            case 0xb :
                            {
                                SetRasterOp( WMFRasterOp::Not );
                                DrawRect( aRect, false );
                                SetRasterOp( WMFRasterOp::CopyPen );
                                Bitmap  aMask( aBitmap );
                                aBitmap.Invert();
                                Bitmap aBmpEx( aBitmap, aMask );
                                ImplDrawBitmap( aPos, aSize, aBmpEx );
                                SetRasterOp( WMFRasterOp::XorPen );
                                ImplDrawBitmap( aPos, aSize, aBitmap );
                                if ( nOperation == 0xb )
                                {
                                    SetRasterOp( WMFRasterOp::Not );
                                    DrawRect( aRect, false );
                                }
                            }
                            break;

                            case 0x2 :
                            case 0xd :
                            {
                                Bitmap  aMask( aBitmap );
                                aMask.Invert();
                                Bitmap aBmpEx( aBitmap, aMask );
                                ImplDrawBitmap( aPos, aSize, aBmpEx );
                                SetRasterOp( WMFRasterOp::XorPen );
                                ImplDrawBitmap( aPos, aSize, aBitmap );
                                if ( nOperation == 0xd )
                                {
                                    SetRasterOp( WMFRasterOp::Not );
                                    DrawRect( aRect, false );
                                }
                            }
                            break;
                            case 0x6 :
                            case 0x9 :
                            {
                                SetRasterOp( WMFRasterOp::XorPen );
                                ImplDrawBitmap( aPos, aSize, aBitmap );
                                if ( nOperation == 0x9 )
                                {
                                    SetRasterOp( WMFRasterOp::Not );
                                    DrawRect( aRect, false );
                                }
                            }
                            break;

                            case 0x0 :  // WHITENESS
                            case 0xf :  // BLACKNESS
                            {                                                   // in this case nRasterOperation is either 0 or 0xff
                                maFillStyle = WinMtfFillStyle( Color( nRasterOperation, nRasterOperation, nRasterOperation ) );
                                UpdateFillStyle();
                                DrawRect( aRect, false );
                            }
                            break;

                            case 0x3 :  // only source is used
                            case 0xc :
                            {
                                if ( nRasterOperation == 0x33 )
                                    aBitmap.Invert();
                                if (pSave->m_bForceAlpha)
                                {
                                    ImplDrawBitmap(aPos, aSize, pSave->aBmp);
                                }
                                else
                                {
                                    ImplDrawBitmap(aPos, aSize, aBitmap);
                                }
                            }
                            break;

                            case 0x5 :  // only destination is used
                            {
                                SetRasterOp( WMFRasterOp::Not );
                                DrawRect( aRect, false );
                            }
                            break;

                            case 0xa :  // no operation
                            break;
                        }
                        SetRasterOp( nOldRop );
                        Pop();
                    }
                }
            }
            nObjectsLeft -= nObjectsOfSameSize;
        }

        rSaveList.clear();
    }

    void MtfTools::SetDevOrg( const Point& rPoint )
    {
        mnDevOrgX = rPoint.X();
        mnDevOrgY = rPoint.Y();
    }

    void MtfTools::SetDevOrgOffset( sal_Int32 nXAdd, sal_Int32 nYAdd )
    {
        mnDevOrgX += nXAdd;
        mnDevOrgY += nYAdd;
    }

    void MtfTools::SetDevExt( const Size& rSize ,bool regular)
    {
        if ( !(rSize.Width() && rSize.Height()) )
            return;

        switch( meMapMode )
        {
            case MappingMode::MM_ISOTROPIC :
            case MappingMode::MM_ANISOTROPIC :
            {
                mnDevWidth = rSize.Width();
                mnDevHeight = rSize.Height();
                break;
            }

            //do nothing
            default:
                break;
        }
        if (regular)
        {
            mbIsMapDevSet=true;
        }
    }

    void MtfTools::ScaleDevExt(double fX, double fY)
    {
        mnDevWidth = basegfx::fround(mnDevWidth * fX);
        mnDevHeight = basegfx::fround(mnDevHeight * fY);
    }

    void MtfTools::SetWinOrg( const Point& rPoint , bool bIsEMF)
    {
        mnWinOrgX = rPoint.X();
        mnWinOrgY = rPoint.Y();
        if (bIsEMF)
        {
            SetDevByWin();
        }
        mbIsMapWinSet=true;
    }

    void MtfTools::SetWinOrgOffset( sal_Int32 nXAdd, sal_Int32 nYAdd )
    {
        mnWinOrgX += nXAdd;
        mnWinOrgY += nYAdd;
    }

    void MtfTools::SetDevByWin() //mnWinExt...-stuff has to be assigned before.
    {
        if (!mbIsMapDevSet)
        {
            if ( meMapMode == MappingMode::MM_ISOTROPIC ) //TODO: WHAT ABOUT ANISOTROPIC???
            {
                sal_Int32 nX, nY;
                if (o3tl::checked_add(mnWinExtX, mnWinOrgX, nX) || o3tl::checked_sub(mnWinExtY, mnWinOrgY, nY))
                    return;
                Size aSize(nX >> MS_FIXPOINT_BITCOUNT_28_4, -(nY >> MS_FIXPOINT_BITCOUNT_28_4));
                SetDevExt(aSize, false);
            }
        }
    }

    void MtfTools::SetWinExt(const Size& rSize, bool bIsEMF)
    {
        if (!(rSize.Width() && rSize.Height()))
            return;

        switch( meMapMode )
        {
            case MappingMode::MM_ISOTROPIC :
            case MappingMode::MM_ANISOTROPIC :
            {
                mnWinExtX = rSize.Width();
                mnWinExtY = rSize.Height();
                if (bIsEMF)
                {
                    SetDevByWin();
                }
                mbIsMapWinSet = true;
                break;
            }

            default:
                //do nothing
                break;
        }
    }

    void MtfTools::ScaleWinExt(double fX, double fY)
    {
        mnWinExtX = basegfx::fround(mnWinExtX * fX);
        mnWinExtY = basegfx::fround(mnWinExtY * fY);
    }

    void MtfTools::SetrclBounds( const tools::Rectangle& rRect )
    {
        mrclBounds = rRect;
    }

    void MtfTools::SetrclFrame( const tools::Rectangle& rRect )
    {
        mrclFrame = rRect;
    }

    void MtfTools::SetRefPix( const Size& rSize )
    {
        mnPixX = rSize.Width();
        mnPixY = rSize.Height();
    }

    void MtfTools::SetRefMill( const Size& rSize )
    {
        mnMillX = rSize.Width();
        mnMillY = rSize.Height();
    }

    void MtfTools::SetMapMode( MappingMode nMapMode )
    {
        meMapMode = nMapMode;
        if ( nMapMode == MappingMode::MM_TEXT && !mbIsMapWinSet )
        {
            mnWinExtX = mnDevWidth;
            mnWinExtY = mnDevHeight;
        }
        else if ( meMapMode == MappingMode::MM_HIMETRIC )
        {
            sal_Int32 nWinExtX, nWinExtY;
            if (o3tl::checked_multiply<sal_Int32>(mnMillX, 100, nWinExtX) ||
                o3tl::checked_multiply<sal_Int32>(mnMillY, 100, nWinExtY))
            {
                return;
            }
            mnWinExtX = nWinExtX;
            mnWinExtY = nWinExtY;
        }
    }

    void MtfTools::SetWorldTransform( const XForm& rXForm )
    {
        maXForm.eM11 = rXForm.eM11;
        maXForm.eM12 = rXForm.eM12;
        maXForm.eM21 = rXForm.eM21;
        maXForm.eM22 = rXForm.eM22;
        maXForm.eDx = rXForm.eDx;
        maXForm.eDy = rXForm.eDy;
    }

    void MtfTools::ModifyWorldTransform( const XForm& rXForm, ModifyWorldTransformMode nMode )
    {
        switch( nMode )
        {
            case ModifyWorldTransformMode::MWT_IDENTITY :
            {
                maXForm.eM11 = maXForm.eM22 = 1.0f;
                maXForm.eM12 = maXForm.eM21 = maXForm.eDx = maXForm.eDy = 0.0f;
                break;
            }

            case ModifyWorldTransformMode::MWT_RIGHTMULTIPLY :
            case ModifyWorldTransformMode::MWT_LEFTMULTIPLY :
            {
                const XForm* pLeft;
                const XForm* pRight;

                if ( nMode == ModifyWorldTransformMode::MWT_LEFTMULTIPLY )
                {
                    pLeft = &rXForm;
                    pRight = &maXForm;
                }
                else
                {
                    pLeft = &maXForm;
                    pRight = &rXForm;
                }

                float aF[3][3];
                float bF[3][3];
                float cF[3][3];

                aF[0][0] = pLeft->eM11;
                aF[0][1] = pLeft->eM12;
                aF[0][2] = 0;
                aF[1][0] = pLeft->eM21;
                aF[1][1] = pLeft->eM22;
                aF[1][2] = 0;
                aF[2][0] = pLeft->eDx;
                aF[2][1] = pLeft->eDy;
                aF[2][2] = 1;

                bF[0][0] = pRight->eM11;
                bF[0][1] = pRight->eM12;
                bF[0][2] = 0;
                bF[1][0] = pRight->eM21;
                bF[1][1] = pRight->eM22;
                bF[1][2] = 0;
                bF[2][0] = pRight->eDx;
                bF[2][1] = pRight->eDy;
                bF[2][2] = 1;

                int i, j, k;
                for ( i = 0; i < 3; i++ )
                {
                  for ( j = 0; j < 3; j++ )
                  {
                     cF[i][j] = 0;
                     for ( k = 0; k < 3; k++ )
                        cF[i][j] += aF[i][k] * bF[k][j];
                  }
                }
                maXForm.eM11 = cF[0][0];
                maXForm.eM12 = cF[0][1];
                maXForm.eM21 = cF[1][0];
                maXForm.eM22 = cF[1][1];
                maXForm.eDx = cF[2][0];
                maXForm.eDy = cF[2][1];
                break;
            }
            case ModifyWorldTransformMode::MWT_SET:
            {
                SetWorldTransform(rXForm);
                break;
            }
        }
    }

    void MtfTools::Push()                       // !! to be able to access the original ClipRegion it
    {                                               // is not allowed to use the MetaPushAction()
        UpdateClipRegion();                         // (the original clip region is on top of the stack) (SJ)
        auto pSave = std::make_shared<SaveStruct>();

        pSave->aLineStyle = maLineStyle;
        pSave->aFillStyle = maFillStyle;

        pSave->aFont = maFont;
        pSave->aTextColor = maTextColor;
        pSave->nTextAlign = mnTextAlign;
        pSave->nTextLayoutMode = mnTextLayoutMode;
        pSave->eMapMode = meMapMode;
        pSave->eGfxMode = meGfxMode;
        pSave->nBkMode = mnBkMode;
        pSave->aBkColor = maBkColor;
        pSave->bClockWiseArcDirection = mbClockWiseArcDirection;
        pSave->bFillStyleSelected = mbFillStyleSelected;

        pSave->aActPos = maActPos;
        pSave->aXForm = maXForm;
        pSave->eRasterOp = meRasterOp;

        pSave->nWinOrgX = mnWinOrgX;
        pSave->nWinOrgY = mnWinOrgY;
        pSave->nWinExtX = mnWinExtX;
        pSave->nWinExtY = mnWinExtY;
        pSave->nDevOrgX = mnDevOrgX;
        pSave->nDevOrgY = mnDevOrgY;
        pSave->nDevWidth = mnDevWidth;
        pSave->nDevHeight = mnDevHeight;

        pSave->maPathObj = maPathObj;
        pSave->maClipPath = maClipPath;

        SAL_INFO("emfio", "\t\t GfxMode: " << static_cast<sal_uInt32>(meGfxMode));
        SAL_INFO("emfio", "\t\t MapMode: " << static_cast<sal_uInt32>(meMapMode));
        SAL_INFO("emfio", "\t\t WinOrg: " << mnWinOrgX << ", " << mnWinOrgY);
        SAL_INFO("emfio", "\t\t WinExt: " << mnWinExtX << " x " << mnWinExtY);
        SAL_INFO("emfio", "\t\t DevOrg: " << mnDevOrgX << ", " << mnDevOrgY);
        SAL_INFO("emfio", "\t\t DevWidth/Height: " << mnDevWidth << " x " << mnDevHeight);
        SAL_INFO("emfio", "\t\t LineStyle: " << maLineStyle.aLineColor << " FillStyle: " << maFillStyle.aFillColor );
        mvSaveStack.push_back(std::move(pSave));
    }

    void MtfTools::Pop( const sal_Int32 nSavedDC )
    {
        if ( nSavedDC == 0 )
            return;

        sal_Int32 aIndex;
        if ( nSavedDC < 0 ) // WMF/EMF, if negative, nSavedDC represents an instance relative to the current state.
            aIndex = static_cast< sal_Int32 >( mvSaveStack.size() ) + nSavedDC;
        else
            aIndex = nSavedDC; // WMF, if positive, nSavedDC represents a specific instance of the state to be restored.
        if( aIndex < 0 )
        {
            mvSaveStack.clear();
            return;
        }
        if( mvSaveStack.empty() || ( aIndex >= static_cast< sal_Int32 >( mvSaveStack.size() ) ) )
            return;

        mvSaveStack.resize( aIndex + 1 );
        // Backup the current data on the stack
        std::shared_ptr<SaveStruct>& pSave( mvSaveStack.back() );

        maLineStyle = pSave->aLineStyle;
        maFillStyle = pSave->aFillStyle;

        maFont = pSave->aFont;
        maTextColor = pSave->aTextColor;
        mnTextAlign = pSave->nTextAlign;
        mnTextLayoutMode = pSave->nTextLayoutMode;
        mnBkMode = pSave->nBkMode;
        meGfxMode = pSave->eGfxMode;
        meMapMode = pSave->eMapMode;
        maBkColor = pSave->aBkColor;
        mbClockWiseArcDirection = pSave->bClockWiseArcDirection;
        mbFillStyleSelected = pSave->bFillStyleSelected;

        maActPos = pSave->aActPos;
        maXForm = pSave->aXForm;
        meRasterOp = pSave->eRasterOp;

        mnWinOrgX = pSave->nWinOrgX;
        mnWinOrgY = pSave->nWinOrgY;
        mnWinExtX = pSave->nWinExtX;
        mnWinExtY = pSave->nWinExtY;
        mnDevOrgX = pSave->nDevOrgX;
        mnDevOrgY = pSave->nDevOrgY;
        mnDevWidth = pSave->nDevWidth;
        mnDevHeight = pSave->nDevHeight;

        maPathObj = pSave->maPathObj;
        if ( ! ( maClipPath == pSave->maClipPath ) )
        {
            maClipPath = pSave->maClipPath;
            mbClipNeedsUpdate = true;
        }
        if ( meLatestRasterOp != meRasterOp )
        {
            mpGDIMetaFile->AddAction( new MetaRasterOpAction( meRasterOp ) );
            meLatestRasterOp = meRasterOp;
        }

        SAL_INFO("emfio", "\t\t GfxMode: " << static_cast<sal_uInt32>(meGfxMode));
        SAL_INFO("emfio", "\t\t MapMode: " << static_cast<sal_uInt32>(meMapMode));
        SAL_INFO("emfio", "\t\t WinOrg: " << mnWinOrgX << ", " << mnWinOrgY);
        SAL_INFO("emfio", "\t\t WinExt: " << mnWinExtX << " x " << mnWinExtY);
        SAL_INFO("emfio", "\t\t DevOrg: " << mnDevOrgX << ", " << mnDevOrgY);
        SAL_INFO("emfio", "\t\t DevWidth/Height: " << mnDevWidth << " x " << mnDevHeight);
        SAL_INFO("emfio", "\t\t LineStyle: " << maLineStyle.aLineColor << " FillStyle: " << maFillStyle.aFillColor );
        mvSaveStack.pop_back();
    }

    void MtfTools::AddFromGDIMetaFile( GDIMetaFile& rGDIMetaFile )
    {
       rGDIMetaFile.Play( *mpGDIMetaFile );
    }

    void MtfTools::PassEMFPlusHeaderInfo()
    {
        EMFP_DEBUG(printf ("\t\t\tadd EMF_PLUS header info\n"));

        SvMemoryStream mem;
        sal_Int32 nLeft, nRight, nTop, nBottom;

        nLeft = mrclFrame.Left();
        nTop = mrclFrame.Top();
        nRight = mrclFrame.Right();
        nBottom = mrclFrame.Bottom();

        // emf header info
        mem.WriteInt32( nLeft ).WriteInt32( nTop ).WriteInt32( nRight ).WriteInt32( nBottom );
        mem.WriteInt32( mnPixX ).WriteInt32( mnPixY ).WriteInt32( mnMillX ).WriteInt32( mnMillY );

        float one, zero;

        one = 1;
        zero = 0;

        // add transformation matrix to be used in vcl's metaact.cxx for
        // rotate and scale operations
        mem.WriteFloat( one ).WriteFloat( zero ).WriteFloat( zero ).WriteFloat( one ).WriteFloat( zero ).WriteFloat( zero );

        // need to flush the stream, otherwise GetEndOfData will return 0
        // on windows where the function parameters are probably resolved in reverse order
        mem.Flush();

        mpGDIMetaFile->AddAction( new MetaCommentAction( "EMF_PLUS_HEADER_INFO"_ostr, 0, static_cast<const sal_uInt8*>(mem.GetData()), mem.GetEndOfData() ) );
        mpGDIMetaFile->UseCanvas( true );
    }

    void MtfTools::PassEMFPlus( void const * pBuffer, sal_uInt32 nLength )
    {
        EMFP_DEBUG(printf ("\t\t\tadd EMF_PLUS comment length %04x\n",(unsigned int) nLength));
        mpGDIMetaFile->AddAction( new MetaCommentAction( "EMF_PLUS"_ostr, 0, static_cast<const sal_uInt8*>(pBuffer), nLength ) );
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
