/* -*- 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 = 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
    };
 
    // 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, 0);
                }
                break;
                case StockObject::BLACK_PEN :
                {
                    maLineStyle = WinMtfLineStyle(COL_BLACK, PS_COSMETIC, 0);
                }
                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() COVERITY_NOEXCEPT_FALSE
    {
        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::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 (BitmapEx(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 ) );
        }
    }
 
    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, 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() ) );
        }
        vcl::Font aTmp( maFont );
        aTmp.SetColor( maTextColor );
        aTmp.SetFillColor( maBkColor );
 
        if( mnBkMode == BackgroundMode::Transparent )
            aTmp.SetTransparent( true );
        else
            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) ) );
            }
        }
 
        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 );
                    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 BitmapEx& rBitmap )
    {
        BitmapEx aBmpEx( rBitmap );
        if ( mbComplexClip )
        {
            vcl::bitmap::DrawAndClipBitmap(rPos, rSize, rBitmap, aBmpEx, maClipPath.getClipPath());
        }
 
        if ( aBmpEx.IsAlpha() )
            mpGDIMetaFile->AddAction( new MetaBmpExScaleAction( rPos, rSize, aBmpEx ) );
        else
            mpGDIMetaFile->AddAction( new MetaBmpScaleAction( rPos, rSize, aBmpEx.GetBitmap() ) );
    }
 
    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->aBmpEx.GetPrefSize() == pSave2->aBmpEx.GetPrefSize() ) &&
                                 ( pSave->aBmpEx.GetPrefMapMode() == pSave2->aBmpEx.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->aBmpEx.GetBitmap() ); aMask.Invert();
                                    BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), 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 & rMask( pSave->aBmpEx.GetBitmap() );
                                    BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), rMask );
                                    ImplDrawBitmap( aPos, aSize, aBmpEx );
                                    bDrawn = true;
                                    i++;
                                }
                                // tdf#90539
                                else if ( ( nWinRop == SRCAND ) && ( pSave2->nWinRop == SRCINVERT ) )
                                {
                                    const Bitmap & rMask( pSave->aBmpEx.GetBitmap() );
                                    BitmapEx aBmpEx( pSave2->aBmpEx.GetBitmap(), rMask );
                                    ImplDrawBitmap( aPos, aSize, aBmpEx );
                                    bDrawn = true;
                                    i++;
                                }
                            }
                        }
                    }
 
                    if ( !bDrawn )
                    {
                        Push();
                        WMFRasterOp nOldRop = SetRasterOp( WMFRasterOp::CopyPen );
                        Bitmap      aBitmap( pSave->aBmpEx.GetBitmap() );
                        sal_uInt32  nOperation = ( nRasterOperation & 0xf );
                        switch( nOperation )
                        {
                            case 0x1 :
                            case 0xe :
                            {
                                if(pSave->aBmpEx.IsAlpha())
                                {
                                    ImplDrawBitmap( aPos, aSize, pSave->aBmpEx );
                                }
                                else
                                {
                                    SetRasterOp( WMFRasterOp::XorPen );
                                    ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) );
                                    SetRasterOp( WMFRasterOp::CopyPen );
                                    Bitmap  aMask( aBitmap );
                                    aMask.Invert();
                                    BitmapEx 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 );
                                }
                                BitmapEx 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();
                                BitmapEx aBmpEx( aBitmap, aMask );
                                ImplDrawBitmap( aPos, aSize, aBmpEx );
                                SetRasterOp( WMFRasterOp::XorPen );
                                ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) );
                                if ( nOperation == 0xb )
                                {
                                    SetRasterOp( WMFRasterOp::Not );
                                    DrawRect( aRect, false );
                                }
                            }
                            break;
 
                            case 0x2 :
                            case 0xd :
                            {
                                Bitmap  aMask( aBitmap );
                                aMask.Invert();
                                BitmapEx aBmpEx( aBitmap, aMask );
                                ImplDrawBitmap( aPos, aSize, aBmpEx );
                                SetRasterOp( WMFRasterOp::XorPen );
                                ImplDrawBitmap( aPos, aSize, BitmapEx(aBitmap) );
                                if ( nOperation == 0xd )
                                {
                                    SetRasterOp( WMFRasterOp::Not );
                                    DrawRect( aRect, false );
                                }
                            }
                            break;
                            case 0x6 :
                            case 0x9 :
                            {
                                SetRasterOp( WMFRasterOp::XorPen );
                                ImplDrawBitmap( aPos, aSize, BitmapEx(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->aBmpEx);
                                }
                                else
                                {
                                    ImplDrawBitmap(aPos, aSize, BitmapEx(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( 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: */

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

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

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

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