/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office 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 <wmfreader.hxx>
#include <emfreader.hxx>

#include <cstdlib>
#include <memory>
#include <optional>
#include <comphelper/configuration.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/sprintf.hxx>
#include <o3tl/unit_conversion.hxx>
#include <rtl/crc.h>
#include <rtl/tencinfo.h>
#include <sal/log.hxx>
#include <osl/endian.h>
#include <vcl/gdimtf.hxx>
#include <vcl/svapp.hxx>
#include <vcl/dibtools.hxx>
#include <vcl/outdev.hxx>
#include <vcl/wmfexternal.hxx>
#include <tools/fract.hxx>
#include <vcl/BitmapReadAccess.hxx>
#include <vcl/BitmapWriteAccess.hxx>
#include <vcl/BitmapTools.hxx>
#include <osl/thread.h>

namespace
{
    // MS Windows defines
    enum WMFRecords
    {
        W_META_EOF                  = 0x0000,
        W_META_SETBKCOLOR           = 0x0201,
        W_META_SETBKMODE            = 0x0102,
        W_META_SETMAPMODE           = 0x0103,
        W_META_SETROP2              = 0x0104,
        W_META_SETRELABS            = 0x0105,
        W_META_SETPOLYFILLMODE      = 0x0106,
        W_META_SETSTRETCHBLTMODE    = 0x0107,
        W_META_SETTEXTCHAREXTRA     = 0x0108,
        W_META_SETTEXTCOLOR         = 0x0209,
        W_META_SETTEXTJUSTIFICATION = 0x020A,
        W_META_SETWINDOWORG         = 0x020B,
        W_META_SETWINDOWEXT         = 0x020C,
        W_META_SETVIEWPORTORG       = 0x020D,
        W_META_SETVIEWPORTEXT       = 0x020E,
        W_META_OFFSETWINDOWORG      = 0x020F,
        W_META_SCALEWINDOWEXT       = 0x0410,
        W_META_OFFSETVIEWPORTORG    = 0x0211,
        W_META_SCALEVIEWPORTEXT     = 0x0412,
        W_META_LINETO               = 0x0213,
        W_META_MOVETO               = 0x0214,
        W_META_EXCLUDECLIPRECT      = 0x0415,
        W_META_INTERSECTCLIPRECT    = 0x0416,
        W_META_ARC                  = 0x0817,
        W_META_ELLIPSE              = 0x0418,
        W_META_FLOODFILL            = 0x0419,
        W_META_PIE                  = 0x081A,
        W_META_RECTANGLE            = 0x041B,
        W_META_ROUNDRECT            = 0x061C,
        W_META_PATBLT               = 0x061D,
        W_META_SAVEDC               = 0x001E,
        W_META_SETPIXEL             = 0x041F,
        W_META_OFFSETCLIPRGN        = 0x0220,
        W_META_TEXTOUT              = 0x0521,
        W_META_BITBLT               = 0x0922,
        W_META_STRETCHBLT           = 0x0B23,
        W_META_POLYGON              = 0x0324,
        W_META_POLYLINE             = 0x0325,
        W_META_ESCAPE               = 0x0626,
        W_META_RESTOREDC            = 0x0127,
        W_META_FILLREGION           = 0x0228,
        W_META_FRAMEREGION          = 0x0429,
        W_META_INVERTREGION         = 0x012A,
        W_META_PAINTREGION          = 0x012B,
        W_META_SELECTCLIPREGION     = 0x012C,
        W_META_SELECTOBJECT         = 0x012D,
        W_META_SETTEXTALIGN         = 0x012E,
        W_META_DRAWTEXT             = 0x062F,
        W_META_CHORD                = 0x0830,
        W_META_SETMAPPERFLAGS       = 0x0231,
        W_META_EXTTEXTOUT           = 0x0a32,
        W_META_SETDIBTODEV          = 0x0d33,
        W_META_SELECTPALETTE        = 0x0234,
        W_META_REALIZEPALETTE       = 0x0035,
        W_META_ANIMATEPALETTE       = 0x0436,
        W_META_SETPALENTRIES        = 0x0037,
        W_META_POLYPOLYGON          = 0x0538,
        W_META_RESIZEPALETTE        = 0x0139,
        W_META_DIBBITBLT            = 0x0940,
        W_META_DIBSTRETCHBLT        = 0x0b41,
        W_META_DIBCREATEPATTERNBRUSH = 0x0142,
        W_META_STRETCHDIB           = 0x0f43,
        W_META_EXTFLOODFILL         = 0x0548,
        W_META_RESETDC              = 0x014C,
        W_META_STARTDOC             = 0x014D,
        W_META_STARTPAGE            = 0x004F,
        W_META_ENDPAGE              = 0x0050,
        W_META_ABORTDOC             = 0x0052,
        W_META_ENDDOC               = 0x005E,
        W_META_DELETEOBJECT         = 0x01f0,
        W_META_CREATEPALETTE        = 0x00f7,
        W_META_CREATEBRUSH          = 0x00F8,
        W_META_CREATEPATTERNBRUSH   = 0x01F9,
        W_META_CREATEPENINDIRECT    = 0x02FA,
        W_META_CREATEFONTINDIRECT   = 0x02FB,
        W_META_CREATEBRUSHINDIRECT  = 0x02FC,
        W_META_CREATEBITMAPINDIRECT = 0x02FD,
        W_META_CREATEBITMAP         = 0x06FE,
        W_META_CREATEREGION         = 0x06FF
    };

    void GetWinExtMax(const Point& rSource, tools::Rectangle& rPlaceableBound, emfio::MappingMode eMapMode)
    {
        Point aSource(rSource);
        if (eMapMode == emfio::MappingMode::MM_HIMETRIC)
            aSource.setY( -rSource.Y() );
        if (aSource.X() < rPlaceableBound.Left())
            rPlaceableBound.SetLeft( aSource.X() );
        if (aSource.X() > rPlaceableBound.Right())
            rPlaceableBound.SetRight( aSource.X() );
        if (aSource.Y() < rPlaceableBound.Top())
            rPlaceableBound.SetTop( aSource.Y() );
        if (aSource.Y() > rPlaceableBound.Bottom())
            rPlaceableBound.SetBottom( aSource.Y() );
    }

    void GetWinExtMax(const tools::Rectangle& rSource, tools::Rectangle& rPlaceableBound, emfio::MappingMode nMapMode)
    {
        GetWinExtMax(rSource.TopLeft(), rPlaceableBound, nMapMode);
        GetWinExtMax(rSource.BottomRight(), rPlaceableBound, nMapMode);
    }

    const char *
    record_type_name(sal_uInt16 nRecType)
    {
    #ifndef SAL_LOG_INFO
        (void) nRecType;
        return "";
    #else
        switch( nRecType )
        {
        case W_META_EOF: return "W_META_EOF";
        case W_META_SETBKCOLOR: return "META_SETBKCOLOR";
        case W_META_SETBKMODE: return "META_SETBKMODE";
        case W_META_SETMAPMODE: return "META_SETMAPMODE";
        case W_META_SETROP2: return "META_SETROP2";
        case W_META_SETRELABS: return "META_SETRELABS";
        case W_META_SETPOLYFILLMODE: return "META_SETPOLYFILLMODE";
        case W_META_SETSTRETCHBLTMODE: return "META_SETSTRETCHBLTMODE";
        case W_META_SETTEXTCHAREXTRA: return "META_SETTEXTCHAREXTRA";
        case W_META_SETTEXTCOLOR: return "META_SETTEXTCOLOR";
        case W_META_SETTEXTJUSTIFICATION: return "META_SETTEXTJUSTIFICATION";
        case W_META_SETWINDOWORG: return "META_SETWINDOWORG";
        case W_META_SETWINDOWEXT: return "META_SETWINDOWEXT";
        case W_META_SETVIEWPORTORG: return "META_SETVIEWPORTORG";
        case W_META_SETVIEWPORTEXT: return "META_SETVIEWPORTEXT";
        case W_META_OFFSETWINDOWORG: return "META_OFFSETWINDOWORG";
        case W_META_SCALEWINDOWEXT: return "META_SCALEWINDOWEXT";
        case W_META_OFFSETVIEWPORTORG: return "META_OFFSETVIEWPORTORG";
        case W_META_SCALEVIEWPORTEXT: return "META_SCALEVIEWPORTEXT";
        case W_META_LINETO: return "META_LINETO";
        case W_META_MOVETO: return "META_MOVETO";
        case W_META_EXCLUDECLIPRECT: return "META_EXCLUDECLIPRECT";
        case W_META_INTERSECTCLIPRECT: return "META_INTERSECTCLIPRECT";
        case W_META_ARC: return "META_ARC";
        case W_META_ELLIPSE: return "META_ELLIPSE";
        case W_META_FLOODFILL: return "META_FLOODFILL";
        case W_META_PIE: return "META_PIE";
        case W_META_RECTANGLE: return "META_RECTANGLE";
        case W_META_ROUNDRECT: return "META_ROUNDRECT";
        case W_META_PATBLT: return "META_PATBLT";
        case W_META_SAVEDC: return "META_SAVEDC";
        case W_META_SETPIXEL: return "META_SETPIXEL";
        case W_META_OFFSETCLIPRGN: return "META_OFFSETCLIPRGN";
        case W_META_TEXTOUT: return "META_TEXTOUT";
        case W_META_BITBLT: return "META_BITBLT";
        case W_META_STRETCHBLT: return "META_STRETCHBLT";
        case W_META_POLYGON: return "META_POLYGON";
        case W_META_POLYLINE: return "META_POLYLINE";
        case W_META_ESCAPE: return "META_ESCAPE";
        case W_META_RESTOREDC: return "META_RESTOREDC";
        case W_META_FILLREGION: return "META_FILLREGION";
        case W_META_FRAMEREGION: return "META_FRAMEREGION";
        case W_META_INVERTREGION: return "META_INVERTREGION";
        case W_META_PAINTREGION: return "META_PAINTREGION";
        case W_META_SELECTCLIPREGION: return "META_SELECTCLIPREGION";
        case W_META_SELECTOBJECT: return "META_SELECTOBJECT";
        case W_META_SETTEXTALIGN: return "META_SETTEXTALIGN";
        case W_META_DRAWTEXT: return "META_DRAWTEXT";
        case W_META_CHORD: return "META_CHORD";
        case W_META_SETMAPPERFLAGS: return "META_SETMAPPERFLAGS";
        case W_META_EXTTEXTOUT: return "META_EXTTEXTOUT";
        case W_META_SETDIBTODEV: return "META_SETDIBTODEV";
        case W_META_SELECTPALETTE: return "META_SELECTPALETTE";
        case W_META_REALIZEPALETTE: return "META_REALIZEPALETTE";
        case W_META_ANIMATEPALETTE: return "META_ANIMATEPALETTE";
        case W_META_SETPALENTRIES: return "META_SETPALENTRIES";
        case W_META_POLYPOLYGON: return "META_POLYPOLYGON";
        case W_META_RESIZEPALETTE: return "META_RESIZEPALETTE";
        case W_META_DIBBITBLT: return "META_DIBBITBLT";
        case W_META_DIBSTRETCHBLT: return "META_DIBSTRETCHBLT";
        case W_META_DIBCREATEPATTERNBRUSH: return "META_DIBCREATEPATTERNBRUSH";
        case W_META_STRETCHDIB: return "META_STRETCHDIB";
        case W_META_EXTFLOODFILL: return "META_EXTFLOODFILL";
        case W_META_RESETDC: return "META_RESETDC";
        case W_META_STARTDOC: return "META_STARTDOC";
        case W_META_STARTPAGE: return "META_STARTPAGE";
        case W_META_ENDPAGE: return "META_ENDPAGE";
        case W_META_ABORTDOC: return "META_ABORTDOC";
        case W_META_ENDDOC: return "META_ENDDOC";
        case W_META_DELETEOBJECT: return "META_DELETEOBJECT";
        case W_META_CREATEPALETTE: return "META_CREATEPALETTE";
        case W_META_CREATEBRUSH: return "META_CREATEBRUSH";
        case W_META_CREATEPATTERNBRUSH: return "META_CREATEPATTERNBRUSH";
        case W_META_CREATEPENINDIRECT: return "META_CREATEPENINDIRECT";
        case W_META_CREATEFONTINDIRECT: return "META_CREATEFONTINDIRECT";
        case W_META_CREATEBRUSHINDIRECT: return "META_CREATEBRUSHINDIRECT";
        case W_META_CREATEBITMAPINDIRECT: return "META_CREATEBITMAPINDIRECT";
        case W_META_CREATEBITMAP: return "META_CREATEBITMAP";
        case W_META_CREATEREGION: return "META_CREATEREGION";
        default:
            // Yes, return a pointer to a static buffer. This is a very
            // local debugging output function, so no big deal.
            static char buffer[11];
            o3tl::sprintf(buffer, "0x%08" SAL_PRIxUINT32, sal_uInt32(nRecType));
            return buffer;
        }
    #endif
    }

    // Reads a Bitmap16 from rStream and returns it as a Bitmap. nHeaderSize
    // is the on-wire size of the header before the Bits field: per
    // [MS-WMF] 2.2.2.1 the structure is 10 bytes; META_CREATEPATTERNBRUSH
    // [MS-WMF] 2.3.4.4 instead wraps it in a 14-byte legacy BITMAP struct
    // (with a 4-byte bmBits placeholder) plus 18 bytes of padding, for a
    // total of 32. Only the 1bpp form actually seen in BS_PATTERN /
    // META_CREATEPATTERNBRUSH is supported; anything else returns an empty
    // Bitmap.
    Bitmap CreateBitmap16(SvStream& rStream, sal_uInt32 nHeaderSize)
    {
        const sal_uInt64 nHdrStart = rStream.Tell();
        sal_Int16 nType(0), nWidth(0), nHeight(0), nWidthBytes(0);
        sal_uInt8 nPlanes(0), nBitsPixel(0);
        rStream.ReadInt16(nType).ReadInt16(nWidth).ReadInt16(nHeight)
               .ReadInt16(nWidthBytes).ReadUChar(nPlanes).ReadUChar(nBitsPixel);
        rStream.Seek(nHdrStart + nHeaderSize);

        if (!rStream.good() || nType != 0 || nPlanes != 1 || nBitsPixel != 1
            || nWidth <= 0 || nHeight <= 0 || nWidthBytes <= 0)
        {
            SAL_WARN("emfio", "\tUnsupported Bitmap16 (Type=" << nType
                << ", Planes=" << int(nPlanes)
                << ", BitsPixel=" << int(nBitsPixel)
                << ", " << nWidth << "x" << nHeight << ")");
            return Bitmap();
        }

        const sal_uInt32 nMinStride = (o3tl::make_unsigned(nWidth) + 7) / 8;
        if (o3tl::make_unsigned(nWidthBytes) < nMinStride)
            return Bitmap();

        const sal_uInt64 nBitsSize = o3tl::make_unsigned(nWidthBytes) * o3tl::make_unsigned(nHeight);
        if (nBitsSize > rStream.remainingSize())
            return Bitmap();

        std::vector<sal_uInt8> aBits(nBitsSize, 0);
        if (rStream.ReadBytes(aBits.data(), aBits.size()) != aBits.size())
            return Bitmap();

        BitmapPalette aPalette(2);
        aPalette[0] = BitmapColor(COL_WHITE);
        aPalette[1] = BitmapColor(COL_BLACK);
        Bitmap aBmp(Size(nWidth, nHeight), vcl::PixelFormat::N8_BPP, &aPalette);
        BitmapScopedWriteAccess pAccess(aBmp);
        if (!pAccess)
            return Bitmap();

        for (sal_Int16 y = 0; y < nHeight; ++y)
        {
            const sal_uInt8* pRow = aBits.data() + o3tl::make_unsigned(y) * o3tl::make_unsigned(nWidthBytes);
            Scanline pScan = pAccess->GetScanline(y);
            for (sal_Int16 x = 0; x < nWidth; ++x)
            {
                // Bit 7 of byte 0 is the leftmost pixel.
                const sal_uInt8 nMask = sal_uInt8(0x80) >> (x & 7);
                pAccess->SetPixelOnData(pScan, x, BitmapColor((pRow[x >> 3] & nMask) ? 1 : 0));
            }
        }
        return aBmp;
    }
}

namespace emfio
{
    inline Point WmfReader::ReadPoint()
    {
        short nX = 0, nY = 0;
        mpInputStream->ReadInt16( nX ).ReadInt16( nY );
        return Point( nX, nY );
    }

    inline Point WmfReader::ReadYX()
    {
        short nX = 0, nY = 0;
        mpInputStream->ReadInt16( nY ).ReadInt16( nX );
        return Point( nX, nY );
    }

    tools::Rectangle WmfReader::ReadRectangle()
    {
        Point aBR, aTL;
        aBR = ReadYX();
        aTL = ReadYX();
        aBR.AdjustX( -1 );
        aBR.AdjustY( -1 );
        if (aTL.X() > aBR.X() || aTL.Y() > aBR.Y())
        {
            SAL_WARN("emfio", "broken rectangle");
            return tools::Rectangle::Normalize(aTL, aBR);
        }
        return tools::Rectangle( aTL, aBR );
    }

    Size WmfReader::ReadYXExt()
    {
        short nW=0, nH=0;
        mpInputStream->ReadInt16( nH ).ReadInt16( nW );
        return Size( nW, nH );
    }

    void WmfReader::ReadRecordParams( sal_uInt32 nRecordSize, sal_uInt16 nFunc )
    {
        bool bRecordOk = true;
        SAL_INFO("emfio", "\t" << record_type_name(nFunc));
        switch(nFunc)
        {
            case W_META_SETBKCOLOR:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                SetBkColor(ReadColor());
            }
            break;

            case W_META_SETBKMODE:
            {
                // It could have Reserved values. Both 4 and 5 sizes are allowed
                if ((nRecordSize != 4) && (nRecordSize != 5))
                    bRecordOk = false;
                sal_uInt16 nDat = 0;
                mpInputStream->ReadUInt16( nDat );
                SetBkMode( static_cast<BackgroundMode>(nDat) );
            }
            break;

            case W_META_SETMAPMODE:
            {
                if (nRecordSize != 4)
                    bRecordOk = false;
                sal_uInt16 nMapMode = 0;
                mpInputStream->ReadUInt16(nMapMode);
                SetMapMode(static_cast<MappingMode>(nMapMode));
            }
            break;

            case W_META_SETROP2:
            {
                // It could have Reserved values. Both 4 and 5 sizes are allowed
                if ((nRecordSize != 4) && (nRecordSize != 5))
                    bRecordOk = false;
                sal_uInt16 nROP2 = 0;
                mpInputStream->ReadUInt16(nROP2);
                SetRasterOp(static_cast<WMFRasterOp>(nROP2));
                mpInputStream->SeekRel(2); // reserved data
            }
            break;

            case W_META_SETTEXTCOLOR:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                SetTextColor( ReadColor() );
            }
            break;

            case W_META_SETWINDOWORG:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                SetWinOrg( ReadYX() );
            }
            break;

            case W_META_SETWINDOWEXT:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                short nWidth = 0, nHeight = 0;
                mpInputStream->ReadInt16( nHeight ).ReadInt16( nWidth );
                SetWinExt( Size( nWidth, nHeight ) );
            }
            break;

            case W_META_OFFSETWINDOWORG:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                short nXAdd = 0, nYAdd = 0;
                mpInputStream->ReadInt16( nYAdd ).ReadInt16( nXAdd );
                SetWinOrgOffset( nXAdd, nYAdd );
            }
            break;

            case W_META_SCALEWINDOWEXT:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                short nXNum = 0, nXDenom = 0, nYNum = 0, nYDenom = 0;
                mpInputStream->ReadInt16( nYDenom ).ReadInt16( nYNum ).ReadInt16( nXDenom ).ReadInt16( nXNum );
                if (!nYDenom || !nXDenom)
                {
                    mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR );
                    break;
                }
                ScaleWinExt( static_cast<double>(nXNum) / nXDenom, static_cast<double>(nYNum) / nYDenom );
            }
            break;

            case W_META_SETVIEWPORTORG:
            case W_META_SETVIEWPORTEXT:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
            }
            break;

            case W_META_OFFSETVIEWPORTORG:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                short nXAdd = 0, nYAdd = 0;
                mpInputStream->ReadInt16( nYAdd ).ReadInt16( nXAdd );
                SetDevOrgOffset( nXAdd, nYAdd );
            }
            break;

            case W_META_SCALEVIEWPORTEXT:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                short nXNum = 0, nXDenom = 0, nYNum = 0, nYDenom = 0;
                mpInputStream->ReadInt16( nYDenom ).ReadInt16( nYNum ).ReadInt16( nXDenom ).ReadInt16( nXNum );
                if (!nYDenom || !nXDenom)
                {
                    mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR );
                    break;
                }
                ScaleDevExt( static_cast<double>(nXNum) / nXDenom, static_cast<double>(nYNum) / nYDenom );
            }
            break;

            case W_META_LINETO:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                LineTo( ReadYX() );
            }
            break;

            case W_META_MOVETO:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                MoveTo( ReadYX() );
            }
            break;

            case W_META_INTERSECTCLIPRECT:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                IntersectClipRect(ReadRectangle());
            }
            break;

            case W_META_RECTANGLE:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                DrawRect(ReadRectangle());
            }
            break;

            case W_META_ROUNDRECT:
            {
                if (nRecordSize != 9)
                    bRecordOk = false;
                Size aSize( ReadYXExt() );
                DrawRoundRect( ReadRectangle(), Size( aSize.Width() / 2, aSize.Height() / 2 ) );
            }
            break;

            case W_META_ELLIPSE:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                DrawEllipse(ReadRectangle());
            }
            break;

            case W_META_ARC:
            {
                if (nRecordSize != 11)
                    bRecordOk = false;
                Point aEnd( ReadYX() );
                Point aStart( ReadYX() );
                tools::Rectangle aRect( ReadRectangle() );
                aRect.Normalize();
                DrawArc( aRect, aStart, aEnd );
            }
            break;

            case W_META_PIE:
            {
                if (nRecordSize != 11)
                    bRecordOk = false;
                Point     aEnd( ReadYX() );
                Point     aStart( ReadYX() );
                tools::Rectangle aRect( ReadRectangle() );
                aRect.Normalize();

                // #i73608# OutputDevice deviates from WMF
                // semantics. start==end means full ellipse here.
                if( aStart == aEnd )
                    DrawEllipse( aRect );
                else
                    DrawPie( aRect, aStart, aEnd );
            }
            break;

            case W_META_CHORD:
            {
                if (nRecordSize != 11)
                    bRecordOk = false;
                Point aEnd( ReadYX() );
                Point aStart( ReadYX() );
                tools::Rectangle aRect( ReadRectangle() );
                aRect.Normalize();
                DrawChord( aRect, aStart, aEnd );
            }
            break;

            case W_META_POLYGON:
            {
                sal_uInt16 nPoints(0);
                mpInputStream->ReadUInt16(nPoints);

                if (nPoints > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16)))
                {
                    bRecordOk = false;
                }
                else
                {
                    tools::Polygon aPoly(nPoints);
                    for (sal_uInt16 i(0); i < nPoints && mpInputStream->good(); ++i)
                        aPoly[ i ] = ReadPoint();
                    DrawPolygon(std::move(aPoly), false/*bRecordPath*/);
                }

                SAL_WARN_IF(!bRecordOk, "emfio", "polygon record has more points than we can handle");

                bRecordOk &= mpInputStream->good();
            }
            break;

            case W_META_POLYPOLYGON:
            {
                sal_uInt16 nPolyCount(0);
                // Number of polygons:
                mpInputStream->ReadUInt16( nPolyCount );
                if (nPolyCount && mpInputStream->good())
                {
                    if (nPolyCount > mpInputStream->remainingSize() / sizeof(sal_uInt16))
                        break;

                    // Number of points of each polygon. Determine total number of points
                    std::unique_ptr<sal_uInt16[]> xPolygonPointCounts(new sal_uInt16[nPolyCount]);
                    sal_uInt16* pnPoints = xPolygonPointCounts.get();
                    tools::PolyPolygon aPolyPoly(nPolyCount);
                    sal_uInt16 nPoints = 0;
                    for (sal_uInt16 a = 0; a < nPolyCount && mpInputStream->good(); ++a)
                    {
                        mpInputStream->ReadUInt16( pnPoints[a] );

                        if (pnPoints[a] > SAL_MAX_UINT16 - nPoints)
                        {
                            bRecordOk = false;
                            break;
                        }

                        nPoints += pnPoints[a];
                    }

                    SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record has more polygons than we can handle");

                    bRecordOk &= mpInputStream->good();

                    if (!bRecordOk)
                        break;

                    // Polygon points are:
                    for (sal_uInt16 a = 0; a < nPolyCount && mpInputStream->good(); ++a)
                    {
                        const sal_uInt16 nPointCount(pnPoints[a]);

                        if (nPointCount > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16)))
                        {
                            bRecordOk = false;
                            break;
                        }

                        std::unique_ptr<Point[]> xPolygonPoints(new Point[nPointCount]);
                        Point* pPtAry = xPolygonPoints.get();

                        for(sal_uInt16 b(0); b < nPointCount && mpInputStream->good(); ++b)
                        {
                            pPtAry[b] = ReadPoint();
                        }

                        aPolyPoly.Insert( tools::Polygon(nPointCount, pPtAry) );
                    }

                    bRecordOk &= mpInputStream->good();

                    if (!bRecordOk)
                        break;

                    DrawPolyPolygon( aPolyPoly );
                }
            }
            break;

            case W_META_POLYLINE:
            {
                sal_uInt16 nPoints(0);
                mpInputStream->ReadUInt16(nPoints);

                if (nPoints > mpInputStream->remainingSize() / (2 * sizeof(sal_uInt16)))
                {
                    bRecordOk = false;
                }
                else
                {
                    tools::Polygon aPoly(nPoints);
                    for (sal_uInt16 i(0); i < nPoints && mpInputStream->good(); ++i)
                        aPoly[ i ] = ReadPoint();
                    DrawPolyLine( std::move(aPoly) );
                }

                SAL_WARN_IF(!bRecordOk, "emfio", "polyline record has more points than we can handle");

                bRecordOk &= mpInputStream->good();
            }
            break;

            case W_META_SAVEDC:
            {
                if (nRecordSize != 3)
                    bRecordOk = false;
                Push();
            }
            break;

            case W_META_RESTOREDC:
            {
                sal_Int16 nSavedDC(0);
                if (nRecordSize != 4)
                    bRecordOk = false;
                mpInputStream->ReadInt16(nSavedDC);
                SAL_INFO("emfio", "\t\t SavedDC: " << nSavedDC);
                SAL_WARN_IF(nSavedDC < 0,  "emfio", "TODO implement relative to the current state");
                Pop(nSavedDC);
            }
            break;

            case W_META_SETPIXEL:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                const Color aColor = ReadColor();
                DrawPixel( ReadYX(), aColor );
            }
            break;

            case W_META_OFFSETCLIPRGN:
            {
                if (nRecordSize != 5)
                    bRecordOk = false;
                MoveClipRegion( ReadYXExt() );
            }
            break;

            case W_META_TEXTOUT:
            {
                //record is Recordsize, RecordFunction, StringLength, <String>, YStart, XStart
                const sal_uInt32 nNonStringLen = sizeof(sal_uInt32) + 4 * sizeof(sal_uInt16);
                const sal_uInt32 nRecSize = nRecordSize * 2;

                if (nRecSize < nNonStringLen)
                {
                    bRecordOk = false;
                    break;
                }

                sal_uInt16 nLength = 0;
                mpInputStream->ReadUInt16(nLength);
                sal_uInt16 nStoredLength = (nLength + 1) &~ 1;

                if (nRecSize - nNonStringLen < nStoredLength)
                {
                    SAL_WARN("emfio", "W_META_TEXTOUT too short, truncating string");
                    nLength = nStoredLength = nRecSize - nNonStringLen;
                }

                if (nLength)
                {
                    std::vector<char> aChars(nStoredLength);
                    nLength = std::min<sal_uInt16>(nLength, mpInputStream->ReadBytes(aChars.data(), aChars.size()));
                    OUString aText(aChars.data(), nLength, GetCharSet());
                    Point aPosition( ReadYX() );
                    DrawText( aPosition, aText );
                }
            }
            break;

            case W_META_EXTTEXTOUT:
            {
                //record is Recordsize, RecordFunction, Y, X, StringLength, options, maybe rectangle, <String>
                sal_uInt32 nNonStringLen = sizeof(sal_uInt32) + 5 * sizeof(sal_uInt16);
                const sal_uInt32 nRecSize = nRecordSize * 2;

                if (nRecSize < nNonStringLen)
                {
                    bRecordOk = false;
                    break;
                }

                auto nRecordPos = mpInputStream->Tell() - 6;
                Point aPosition = ReadYX();
                sal_uInt16 nLen = 0, nOptions = 0;
                mpInputStream->ReadUInt16( nLen ).ReadUInt16( nOptions );
                SAL_INFO( "emfio", "\t\t\t Pos: " << aPosition.getX() << ":" << aPosition.getY() << " String Length: " << nLen << " Options: " << nOptions );
                tools::Rectangle aRect;
                if ( ( nOptions & ETO_CLIPPED ) || ( nOptions & ETO_OPAQUE ) )
                {
                    nNonStringLen += 2 * sizeof(sal_uInt16);

                    if (nRecSize < nNonStringLen)
                    {
                        bRecordOk = false;
                        break;
                    }
                    const Point aTopLeft = ReadPoint();
                    const Point aBottomRight = ReadPoint();
                    aRect = tools::Rectangle( aTopLeft, aBottomRight );
                    if ( nOptions & ETO_OPAQUE )
                        DrawRectWithBGColor( aRect );
                    SAL_INFO( "emfio", "\t\t\t Rectangle : " << aTopLeft.getX() << ":" << aTopLeft.getY() << ", " << aBottomRight.getX() << ":" << aBottomRight.getY() );
                }

                vcl::text::ComplexTextLayoutFlags nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::Default;
                if ( nOptions & ETO_RTLREADING )
                    nTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
                SetTextLayoutMode( nTextLayoutMode );
                SAL_WARN_IF( ( nOptions & ( ETO_PDY | ETO_GLYPH_INDEX ) ) != 0, "emfio", "SJ: ETO_PDY || ETO_GLYPH_INDEX in WMF" );

                // output only makes sense if the text contains characters
                if (nLen)
                {
                    sal_Int32 nOriginalTextLen = nLen;
                    sal_Int32 nOriginalBlockLen = ( nOriginalTextLen + 1 ) &~ 1;

                    auto nMaxStreamPos = nRecordPos + nRecSize;
                    auto nRemainingSize = std::min(mpInputStream->remainingSize(), nMaxStreamPos - mpInputStream->Tell());
                    if (nRemainingSize < o3tl::make_unsigned(nOriginalBlockLen))
                    {
                        SAL_WARN("emfio", "exttextout record claimed more data than the stream can provide");
                        nOriginalTextLen = nOriginalBlockLen = nRemainingSize;
                    }

                    std::vector<char> pChar(nOriginalBlockLen);
                    mpInputStream->ReadBytes(pChar.data(), nOriginalBlockLen);
                    OUString aText(pChar.data(), nOriginalTextLen, GetCharSet()); // after this conversion the text may contain
                    sal_Int32 nNewTextLen = aText.getLength();                         // less character (japanese version), so the
                                                                                       // dxAry will not fit
                    if ( nNewTextLen )
                    {
                        if ( nOptions & ETO_CLIPPED )
                        {
                            Push(); // Save the current clip. It will be restored after text drawing
                            IntersectClipRect( aRect );
                        }
                        SAL_INFO( "emfio", "\t\t\t Text : " << aText );
                        KernArray aDXAry;
                        std::unique_ptr<tools::Long[]> pDYAry;
                        auto nDxArySize =  nMaxStreamPos - mpInputStream->Tell();
                        auto nDxAryEntries = nDxArySize >> 1;
                        bool        bUseDXAry = false;

                        if ( ( ( nDxAryEntries % nOriginalTextLen ) == 0 ) && ( nNewTextLen <= nOriginalTextLen ) )
                        {
                            sal_Int32 i; // needed just outside the for
                            aDXAry.resize( nNewTextLen );
                            if ( nOptions & ETO_PDY )
                            {
                                pDYAry.reset(new tools::Long[ nNewTextLen ]);
                            }
                            for (i = 0; i < nNewTextLen; i++ )
                            {
                                if ( mpInputStream->Tell() >= nMaxStreamPos )
                                    break;
                                sal_Int32 nDxCount = 1;
                                if ( nNewTextLen != nOriginalTextLen )
                                {
                                    sal_Unicode cUniChar = aText[i];
                                    OString aTmp(&cUniChar, 1, GetCharSet());
                                    if ( aTmp.getLength() > 1 )
                                    {
                                        nDxCount = aTmp.getLength();
                                    }
                                }

                                sal_Int16 nDx = 0, nDy = 0;
                                while ( nDxCount-- )
                                {
                                    if ( ( mpInputStream->Tell() + 2 ) > nMaxStreamPos )
                                        break;
                                    sal_Int16 nDxTmp = 0;
                                    mpInputStream->ReadInt16(nDxTmp);
                                    nDx += nDxTmp;
                                    if ( nOptions & ETO_PDY )
                                    {
                                        if ( ( mpInputStream->Tell() + 2 ) > nMaxStreamPos )
                                            break;
                                        sal_Int16 nDyTmp = 0;
                                        mpInputStream->ReadInt16(nDyTmp);
                                        nDy += nDyTmp;
                                    }
                                }

                                aDXAry[i] = nDx;
                                if ( nOptions & ETO_PDY )
                                {
                                    pDYAry[i] = nDy;
                                }
                            }
                            if ( i == nNewTextLen )
                                bUseDXAry = true;
                        }
                        if ( bUseDXAry )
                            DrawText( aPosition, aText, &aDXAry, pDYAry.get() );
                        else
                            DrawText( aPosition, aText );
                        if ( nOptions & ETO_CLIPPED )
                            Pop();
                    }
                }
            }
            break;

            case W_META_SELECTOBJECT:
            case W_META_SELECTPALETTE:
            {
                if (nRecordSize != 4)
                    bRecordOk = false;
                sal_uInt16   nObjIndex = 0;
                mpInputStream->ReadUInt16( nObjIndex );
                SelectObject( nObjIndex );
            }
            break;

            case W_META_SETTEXTALIGN:
            {
                // It could have Reserved values. Both 4 and 5 sizes are allowed
                if ((nRecordSize != 4) && (nRecordSize != 5))
                    bRecordOk = false;
                sal_uInt16  nAlign = 0;
                mpInputStream->ReadUInt16( nAlign );
                SetTextAlign( nAlign );
            }
            break;

            case W_META_BITBLT:
            case W_META_STRETCHBLT:
            {
                sal_uInt32  nRasterOperation = 0;
                sal_Int16   nSrcHeight = 0, nSrcWidth = 0, nYSrc, nXSrc, nSye, nSxe, nBitmapType, nWidth, nHeight, nBytesPerScan;
                sal_uInt8   nPlanes, nBitCount;
                const bool bNoSourceBitmap = ( nRecordSize == ( static_cast< sal_uInt32 >( nFunc ) >> 8 ) + 3 );

                mpInputStream->ReadUInt32( nRasterOperation );
                SAL_INFO("emfio", "\t\t Raster operation: 0x" << std::hex << nRasterOperation << std::dec << ", No source bitmap: " << bNoSourceBitmap);

                if (nFunc == W_META_STRETCHBLT)
                {
                    if (nRecordSize < 18)
                        bRecordOk = false;
                    mpInputStream->ReadInt16(nSrcHeight).ReadInt16(nSrcWidth);
                }
                else
                    if (nRecordSize < 16)
                        bRecordOk = false;
                mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc );
                if ( bNoSourceBitmap )
                    mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored)
                mpInputStream->ReadInt16( nSye ).ReadInt16( nSxe );
                Point aPoint( ReadYX() ); // The upper-left corner of the destination rectangle.
                mpInputStream->ReadInt16( nBitmapType ).ReadInt16( nWidth ).ReadInt16( nHeight ).ReadInt16( nBytesPerScan ).ReadUChar( nPlanes ).ReadUChar( nBitCount );

                SAL_INFO("emfio", "\t\t Bitmap type:" << nBitmapType << " Width:" << nWidth << " Height:" << nHeight << " WidthBytes:" << nBytesPerScan << " Planes: " << static_cast< sal_uInt16 >( nPlanes ) << " BitCount: " << static_cast< sal_uInt16 >( nBitCount ) );
                if (!mpInputStream->good() || bNoSourceBitmap || nBitCount == 4 || nBitCount == 8 || nPlanes != 1)
                {
                    SAL_WARN("emfio", "\t\t TODO The unsupported Bitmap record. Please fill a bug.");
                    break;
                }
                bool bOk = nWidth > 0 && nHeight > 0 && nBytesPerScan > 0
                    && (nBitCount == 1 || nBitCount == 8 || nBitCount == 24 || nBitCount == 32);
                if (bOk)
                {
                    // must be enough data to fulfil the request
                    bOk = o3tl::make_unsigned( nBytesPerScan ) <= mpInputStream->remainingSize() / nHeight;
                }
                if (bOk)
                {
                    // scanline must be large enough to provide all pixels
                    bOk = nBytesPerScan >= nWidth * nBitCount / 8;
                }
                if (bOk)
                {
                    std::unique_ptr< sal_uInt8[] > pData;
                    pData.reset( new sal_uInt8[ nHeight * nBytesPerScan ] );
                    mpInputStream->ReadBytes( pData.get(), nHeight * nBytesPerScan );
                    Bitmap aBitmap = vcl::bitmap::CreateFromData( pData.get(), nWidth, nHeight, nBytesPerScan, nBitCount, true );
                    if ( nSye && nSxe &&
                         ( nXSrc + nSxe <= nWidth ) &&
                         ( nYSrc + nSye <= nHeight ) )
                    {
                        tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSxe, nSye ) );
                        aBitmap.Crop( aCropRect );
                    }
                    tools::Rectangle aDestRect( aPoint, Size( nSxe, nSye ) );
                    maBmpSaveList.emplace_back(aBitmap, aDestRect, nRasterOperation);
                }
            }
            break;

            case W_META_DIBBITBLT:
            case W_META_DIBSTRETCHBLT:
            case W_META_STRETCHDIB:
            {
                sal_uInt32  nRasterOperation = 0;
                sal_uInt16  nColorUsage = 0;
                sal_Int16   nSrcHeight = 0, nSrcWidth = 0, nYSrc = 0, nXSrc = 0;
                Bitmap      aBmp;
                const bool bNoSourceBitmap = ( nFunc != W_META_STRETCHDIB ) && ( nRecordSize == ( ( static_cast< sal_uInt32 >( nFunc ) >> 8 ) + 3 ) );

                if (nRecordSize < 12)
                    bRecordOk = false;
                mpInputStream->ReadUInt32( nRasterOperation );
                SAL_INFO("emfio", "\t\t Raster operation: 0x" << std::hex << nRasterOperation << std::dec << ", No source bitmap: " << bNoSourceBitmap);
                if (nFunc == W_META_STRETCHDIB)
                {
                    if (nRecordSize < 15)
                        bRecordOk = false;
                    mpInputStream->ReadUInt16(nColorUsage);
                }

                // nSrcHeight and nSrcWidth is the number of pixels that has to been used
                // If they are set to zero, it is as indicator not to scale the bitmap later
                if( nFunc == W_META_DIBSTRETCHBLT ||
                    nFunc == W_META_STRETCHDIB )
                    mpInputStream->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth );

                // nYSrc and nXSrc is the offset of the first pixel
                mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc );

                if ( bNoSourceBitmap )
                    mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored)

                Size aDestSize( ReadYXExt() );
                if ( aDestSize.Width() && aDestSize.Height() )  // #92623# do not try to read buggy bitmaps
                {
                    tools::Rectangle aDestRect( ReadYX(), aDestSize );
                    if ( !bNoSourceBitmap )
                    {
                        // tdf#142625 Read the DIBHeader and check if bitmap is supported
                        // If bitmap is not supported don't run ReadDIB, as it will interrupt image processing
                        const auto nOldPos(mpInputStream->Tell());
                        sal_uInt32 nHeaderSize(0);
                        mpInputStream->ReadUInt32( nHeaderSize );
                        if ( nHeaderSize == 0xC ) // BitmapCoreHeader
                            mpInputStream->SeekRel( 6 ); // skip Width (16), Height (16), Planes (16)
                        else
                            mpInputStream->SeekRel( 10 ); // skip Width (32), Height (32), Planes (16)
                        sal_uInt16 nBitCount(0);
                        mpInputStream->ReadUInt16( nBitCount );
                        if ( nBitCount == 0 ) // TODO Undefined BitCount (JPEG/PNG), which are not supported
                            break;
                        mpInputStream->Seek(nOldPos);

                        if ( !ReadDIB( aBmp, *mpInputStream, false ) )
                            SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." );
                    }
                    // test if it is sensible to crop
                    if ( nSrcHeight && nSrcWidth &&
                            ( nXSrc + nSrcWidth <= aBmp.GetSizePixel().Width() ) &&
                            ( nYSrc + nSrcHeight <= aBmp.GetSizePixel().Height() ) )
                    {
                        tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSrcWidth, nSrcHeight ) );
                        aBmp.Crop( aCropRect );
                    }

                    maBmpSaveList.emplace_back(aBmp, aDestRect, nRasterOperation);
                }
            }
            break;

            case W_META_DIBCREATEPATTERNBRUSH:
            {
                Bitmap  aBmp;
                sal_uInt16 nStyle(0), nColorUsage(0);

                if (nRecordSize < 5)
                    bRecordOk = false;
                mpInputStream->ReadUInt16( nStyle ).ReadUInt16( nColorUsage );
                BrushStyle eStyle = static_cast<BrushStyle>(nStyle);
                SAL_INFO( "emfio", "\t\t Style:" << nStyle << ", ColorUsage: " << nColorUsage );
                // Per [MS-WMF] 2.3.4.8, BS_PATTERN means the Target is a
                // Bitmap16 and other styles mean a DIB. In practice many
                // generators write a DIB even with Style == BS_PATTERN, so
                // peek at the leading DWORD: a known DIB header size means
                // a DIB; otherwise fall back to Bitmap16.
                bool bIsDib = true;
                if ( eStyle == BrushStyle::BS_PATTERN )
                {
                    // The leading DWORD of a DIB header is its own size,
                    // and only a small set of values is valid:
                    //   12  BITMAPCOREHEADER
                    //   40  BITMAPINFOHEADER
                    //   52  BITMAPV2INFOHEADER (Adobe extension)
                    //   56  BITMAPV3INFOHEADER (Adobe extension)
                    //   64  OS22XBITMAPHEADER (OS/2 2.x)
                    //   108 BITMAPV4HEADER
                    //   124 BITMAPV5HEADER
                    // A leading DWORD outside this set means the Target is
                    // a Bitmap16, not a DIB.
                    sal_uInt32 nProbe(0);
                    const sal_uInt64 nProbePos = mpInputStream->Tell();
                    mpInputStream->ReadUInt32( nProbe );
                    mpInputStream->Seek( nProbePos );
                    bIsDib = (nProbe == 12 || nProbe == 40 || nProbe == 52
                            || nProbe == 56 || nProbe == 64 || nProbe == 108
                            || nProbe == 124);
                }
                if ( !bIsDib )
                    aBmp = CreateBitmap16( *mpInputStream, 10 );
                else if ( !ReadDIB( aBmp, *mpInputStream, false ) )
                    SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." );
                if ( aBmp.IsEmpty() )
                    CreateObject();
                else
                    CreateObject(std::make_unique<WinMtfFillStyle>( aBmp ));
            }
            break;

            case W_META_DELETEOBJECT:
            {
                if (nRecordSize != 4)
                    bRecordOk = false;
                sal_uInt16 nIndex = 0;
                mpInputStream->ReadUInt16( nIndex );
                DeleteObject( nIndex );
            }
            break;

            case W_META_CREATEPALETTE:
            {
                sal_uInt16 nStart = 0;
                sal_uInt16 nNumberOfEntries = 0;
                mpInputStream->ReadUInt16(nStart);
                mpInputStream->ReadUInt16(nNumberOfEntries);

                if (nRecordSize != 2u * nNumberOfEntries + 5u)
                    bRecordOk = false;
                SAL_INFO("emfio", "\t\t Start 0x" << std::hex << nStart << std::dec << ", Number of entries: " << nNumberOfEntries);
                std::vector< Color > aPaletteColors;
                for (sal_uInt16 i = 0; i < nNumberOfEntries && mpInputStream->good(); ++i)
                {
                    //PALETTEENTRY: Values, Blue, Green, Red
                    sal_uInt32 nPalleteEntry(0);
                    mpInputStream->ReadUInt32( nPalleteEntry );
                    SAL_INFO("emfio", "\t\t " << i << ". Palette entry: " << std::setw(10) << std::showbase <<std::hex << nPalleteEntry << std::dec );
                    aPaletteColors.emplace_back(static_cast<sal_uInt8>(nPalleteEntry), static_cast<sal_uInt8>(nPalleteEntry >> 8), static_cast<sal_uInt8>(nPalleteEntry >> 16));
                }
                CreateObject(std::make_unique<WinMtfPalette>( aPaletteColors ));
            }
            break;

            case W_META_CREATEBRUSH:
            {
                SAL_WARN( "emfio", "TODO: Not implemented. Please fill the bug report" );
                CreateObject(std::make_unique<WinMtfFillStyle>( COL_WHITE, false ));
            }
            break;

            case W_META_CREATEPATTERNBRUSH:
            {
                // [MS-WMF] 2.3.4.4: Pattern is a 32-byte legacy BITMAP
                // struct (with bmBits placeholder + reserved padding)
                // followed by the actual bits.
                Bitmap aBmp = CreateBitmap16( *mpInputStream, 32 );
                if ( aBmp.IsEmpty() )
                    CreateObject(std::make_unique<WinMtfFillStyle>( COL_WHITE, false ));
                else
                    CreateObject(std::make_unique<WinMtfFillStyle>( aBmp ));
            }
            break;

            case W_META_CREATEPENINDIRECT:
            {
                // FIXME For some WMF correct size is 8 and for some 9
                if ((nRecordSize != 8) && (nRecordSize != 9))
                    bRecordOk = false;
                LineInfo aLineInfo;
                sal_uInt16 nStyle = 0;
                sal_uInt16 nWidth = 0;
                sal_uInt16 nHeight = 0;

                mpInputStream->ReadUInt16(nStyle);
                mpInputStream->ReadUInt16(nWidth);
                mpInputStream->ReadUInt16(nHeight);
                CreateObject(std::make_unique<WinMtfLineStyle>(ReadColor(), nStyle, nWidth));
            }
            break;

            case W_META_CREATEBRUSHINDIRECT:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                sal_uInt16 nBrushStyle = 0;
                mpInputStream->ReadUInt16( nBrushStyle );
                BrushStyle eBrushStyle = static_cast<BrushStyle>(nBrushStyle);
                Color aColor = ReadColor();
                sal_uInt16 nHatch(0);
                mpInputStream->ReadUInt16( nHatch );
                if (eBrushStyle == BrushStyle::BS_HATCHED)
                    CreateObject(std::make_unique<WinMtfFillStyle>( aColor, mapWindowsHatch(nHatch, aColor) ));
                else
                    CreateObject(std::make_unique<WinMtfFillStyle>( aColor, eBrushStyle == BrushStyle::BS_NULL ));
            }
            break;

            case W_META_CREATEFONTINDIRECT:
            {
                Size aFontSize;
                char lfFaceName[LF_FACESIZE+1];
                sal_Int16 lfEscapement = 0;
                sal_Int16 lfOrientation = 0;
                sal_Int16 lfWeight = 0;

                LOGFONTW aLogFont;
                aFontSize = ReadYXExt();
                mpInputStream->ReadInt16( lfEscapement );
                mpInputStream->ReadInt16( lfOrientation );
                mpInputStream->ReadInt16( lfWeight );
                mpInputStream->ReadUChar( aLogFont.lfItalic );
                mpInputStream->ReadUChar( aLogFont.lfUnderline );
                mpInputStream->ReadUChar( aLogFont.lfStrikeOut );
                mpInputStream->ReadUChar( aLogFont.lfCharSet );
                mpInputStream->ReadUChar( aLogFont.lfOutPrecision );
                mpInputStream->ReadUChar( aLogFont.lfClipPrecision );
                mpInputStream->ReadUChar( aLogFont.lfQuality );
                mpInputStream->ReadUChar( aLogFont.lfPitchAndFamily );
                size_t nRet = mpInputStream->ReadBytes( lfFaceName, LF_FACESIZE );
                lfFaceName[nRet] = 0;
                aLogFont.lfWidth = aFontSize.Width();
                aLogFont.lfHeight = aFontSize.Height();
                aLogFont.lfEscapement = lfEscapement;
                aLogFont.lfOrientation = lfOrientation;
                aLogFont.lfWeight = lfWeight;

                rtl_TextEncoding eCharSet;
                if ( ( aLogFont.lfCharSet == OEM_CHARSET ) || ( aLogFont.lfCharSet == DEFAULT_CHARSET ) )
                    eCharSet = osl_getThreadTextEncoding();
                else
                    eCharSet = rtl_getTextEncodingFromWindowsCharset( aLogFont.lfCharSet );
                if ( eCharSet == RTL_TEXTENCODING_DONTKNOW )
                    eCharSet = osl_getThreadTextEncoding();
                if ( eCharSet == RTL_TEXTENCODING_SYMBOL )
                    eCharSet = RTL_TEXTENCODING_MS_1252;
                size_t nLength = strlen(lfFaceName);
                aLogFont.alfFaceName = OUString( lfFaceName, nLength, eCharSet );
                SAL_INFO("emfio", "\tFacename: " << lfFaceName);

                if ((nRecordSize < 12) || (nRecordSize > 28))
                    bRecordOk = false;
                else
                    CreateObject(std::make_unique<WinMtfFontStyle>(aLogFont));
            }
            break;

            case W_META_CREATEBITMAPINDIRECT:
            {
                SAL_WARN( "emfio", "TODO: W_META_CREATEBITMAPINDIRECT is not implemented. Please fill the bug report" );
                CreateObject();
            }
            break;

            case W_META_CREATEBITMAP:
            {
                SAL_WARN( "emfio", "TODO: W_META_CREATEBITMAP is not implemented. Please fill the bug report" );
                CreateObject();
            }
            break;

            case W_META_CREATEREGION:
            {
                SAL_WARN( "emfio", "TODO: W_META_CREATEREGION is not implemented. Please fill the bug report" );
                CreateObject();
            }
            break;

            case W_META_EXCLUDECLIPRECT:
            {
                if (nRecordSize != 7)
                    bRecordOk = false;
                SAL_WARN("emfio", "TODO:  Not working correctly. Please fill the bug report");
                ExcludeClipRect(ReadRectangle());
            }
            break;

            case W_META_PATBLT:
            {
                if (nRecordSize != 9)
                    bRecordOk = false;
                sal_uInt32 nROP = 0;
                mpInputStream->ReadUInt32( nROP );
                Size aSize = ReadYXExt();
                WMFRasterOp nOldROP = SetRasterOp( static_cast<WMFRasterOp>(nROP) );
                DrawRect( tools::Rectangle( ReadYX(), aSize ), false );
                SetRasterOp( nOldROP );
            }
            break;

            case W_META_SELECTCLIPREGION:
            {
                if (nRecordSize != 4)
                    bRecordOk = false;
                sal_uInt16 nObjIndex = 0;
                mpInputStream->ReadUInt16( nObjIndex );
                SAL_WARN( "emfio", "TODO: W_META_SELECTCLIPREGION is not implemented. Please fill the bug report" );
                if ( !nObjIndex )
                {
                    tools::PolyPolygon aEmptyPolyPoly;
                    SetClipPath( aEmptyPolyPoly, RegionMode::RGN_COPY, true );
                }
            }
            break;

            case W_META_ESCAPE:
            {
                sal_uInt64 nMetaRecSize = static_cast<sal_uInt64>(nRecordSize - 2) * 2;
                sal_uInt64 nMetaRecEndPos = mpInputStream->Tell() + nMetaRecSize;

                // taking care that nRecordSize does not exceed the maximal stream position
                if (nMetaRecEndPos > mnEndPos)
                {
                    mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR);
                    break;
                }
                sal_uInt16 nMode = 0, nLen = 0;
                mpInputStream->ReadUInt16(nMode).ReadUInt16(nLen);
                if (nRecordSize != ((nLen + 1u) >> 1u) + 5u)
                {
                    bRecordOk = false;
                    break;
                }
                if ((nMode == W_MFCOMMENT) && (nLen >= 4))
                {
                    sal_uInt32 nNewMagic = 0; // we have to read int32 for
                    // META_ESCAPE_ENHANCED_METAFILE CommentIdentifier
                    mpInputStream->ReadUInt32(nNewMagic);

                    if (nNewMagic == 0x2c2a4f4f && nLen >= 14)
                    {
                        sal_uInt16 nMagic2 = 0;
                        mpInputStream->ReadUInt16(nMagic2);
                        if (nMagic2 == 0x0a) // 2nd half of magic
                        { // continue with private escape
                            sal_uInt32 nCheck = 0, nEsc = 0;
                            mpInputStream->ReadUInt32(nCheck).ReadUInt32(nEsc);

                            sal_uInt32 nEscLen = nLen - 14;
                            if (nEscLen <= (nRecordSize * 2))
                            {
#ifdef OSL_BIGENDIAN
                                sal_uInt32 nTmp = OSL_SWAPDWORD(nEsc);
                                sal_uInt32 nCheckSum = rtl_crc32(0, &nTmp, 4);
#else
                                sal_uInt32 nCheckSum = rtl_crc32(0, &nEsc, 4);
#endif
                                std::unique_ptr<sal_Int8[]> pData;

                                if ((static_cast<sal_uInt64>(nEscLen) + mpInputStream->Tell())
                                    > nMetaRecEndPos)
                                {
                                    mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR);
                                    break;
                                }
                                if (nEscLen > 0)
                                {
                                    pData.reset(new sal_Int8[nEscLen]);
                                    mpInputStream->ReadBytes(pData.get(), nEscLen);
                                    nCheckSum = rtl_crc32(nCheckSum, pData.get(), nEscLen);
                                }
                                if (comphelper::IsFuzzing() || nCheck == nCheckSum)
                                {
                                    switch (nEsc)
                                    {
                                        case PRIVATE_ESCAPE_UNICODE:
                                        {
                                            // we will use text instead of polygons only if we have the correct font
                                            if (comphelper::IsFuzzing()
                                                || Application::GetDefaultDevice()->IsFontAvailable(
                                                       GetFont().GetFamilyName()))
                                            {
                                                Point aPt;
                                                sal_uInt32 nStringLen(0), nDXCount(0);
                                                KernArray aDXAry;
                                                SvMemoryStream aMemoryStream(nEscLen);
                                                aMemoryStream.WriteBytes(pData.get(), nEscLen);
                                                aMemoryStream.Seek(STREAM_SEEK_TO_BEGIN);
                                                sal_Int32 nTmpX(0), nTmpY(0);
                                                aMemoryStream.ReadInt32(nTmpX)
                                                    .ReadInt32(nTmpY)
                                                    .ReadUInt32(nStringLen);
                                                aPt.setX(nTmpX);
                                                aPt.setY(nTmpY);

                                                if ((static_cast<sal_uInt64>(nStringLen)
                                                    * sizeof(sal_Unicode))
                                                    < (nEscLen - aMemoryStream.Tell()))
                                                {
                                                    OUString aString = read_uInt16s_ToOUString(
                                                        aMemoryStream, nStringLen);
                                                    aMemoryStream.ReadUInt32(nDXCount);
                                                    if (nDXCount < o3tl::make_unsigned(aString.getLength()))
                                                        nDXCount = 0;
                                                    if ((static_cast<sal_uInt64>(nDXCount)
                                                        * sizeof(sal_Int32))
                                                        >= (nEscLen - aMemoryStream.Tell()))
                                                        nDXCount = 0;
                                                    if (nDXCount)
                                                        aDXAry.resize(nDXCount);
                                                    for (sal_uInt32 i = 0; i < nDXCount; i++)
                                                    {
                                                        sal_Int32 val(0);
                                                        aMemoryStream.ReadInt32(val);
                                                        aDXAry[i] = val;
                                                    }
                                                    aMemoryStream.ReadUInt32(mnSkipActions);
                                                    DrawText(aPt, aString,
                                                             aDXAry.empty() ? nullptr : &aDXAry);
                                                }
                                            }
                                        }
                                        break;
                                    }
                                }
                            }
                        }
                    }
                    else if ((nNewMagic == static_cast<sal_uInt32>(0x43464D57)) && (nLen >= 34)
                            && (static_cast<sal_Int32>(nLen + 10)
                                <= static_cast<sal_Int32>(nRecordSize * 2)))
                    {
                        sal_uInt32 nComType = 0, nVersion = 0, nFlags = 0, nComRecCount = 0,
                                nCurRecSize = 0, nRemainingSize = 0, nEMFTotalSize = 0;
                        sal_uInt16 nCheck = 0;

                        mpInputStream->ReadUInt32(nComType)
                            .ReadUInt32(nVersion)
                            .ReadUInt16(nCheck)
                            .ReadUInt32(nFlags)
                            .ReadUInt32(nComRecCount)
                            .ReadUInt32(nCurRecSize)
                            .ReadUInt32(nRemainingSize)
                            .ReadUInt32(
                                nEMFTotalSize); // the nRemainingSize is not mentioned in MSDN documentation
                            // but it seems to be required to read in data produced by OLE

                        if (nComType == 0x01 && nVersion == 0x10000 && nComRecCount)
                        {
                            if (!mnEMFRec)
                            { // first EMF comment
                                mnEMFRecCount = nComRecCount;
                                mnEMFSize = nEMFTotalSize;
                                if (mnEMFSize > mpInputStream->remainingSize())
                                {
                                    SAL_WARN("emfio",
                                            "emf size claims to be larger than remaining data");
                                    mpEMFStream.reset();
                                }
                                else
                                    mpEMFStream = std::vector<sal_uInt8>();
                            }
                            else if ((mnEMFRecCount != nComRecCount)
                                    || (mnEMFSize != nEMFTotalSize)) // add additional checks here
                            {
                                // total records should be the same as in previous comments
                                mnEMFRecCount = 0xFFFFFFFF;
                                mpEMFStream.reset();
                            }
                            mnEMFRec++;

                            if (mpEMFStream && nCurRecSize + 34 > nLen)
                            {
                                mnEMFRecCount = 0xFFFFFFFF;
                                mpEMFStream.reset();
                            }

                            if (mpEMFStream && nCurRecSize > mpInputStream->remainingSize())
                            {
                                SAL_WARN("emfio",
                                        "emf record size claims to be larger than remaining data");
                                mnEMFRecCount = 0xFFFFFFFF;
                                mpEMFStream.reset();
                            }

                            if (mpEMFStream)
                            {
                                std::vector<sal_Int8> aBuf(nCurRecSize);
                                sal_uInt32 nCount = mpInputStream->ReadBytes(aBuf.data(), nCurRecSize);
                                if (nCount == nCurRecSize)
                                {
                                    mpEMFStream->insert(mpEMFStream->end(), aBuf.begin(), aBuf.end());
                                }
                            }
                        }
                    }
                }
            }
            break;

            case W_META_SETPOLYFILLMODE:
            {
                if ((nRecordSize != 4) && (nRecordSize != 5))
                    bRecordOk = false;
                sal_uInt16 nDat = 0;
                mpInputStream->ReadUInt16( nDat );
                SetPolyFillMode( nDat );
            }
            break;

            case W_META_SETRELABS:
            case W_META_SETSTRETCHBLTMODE:
            case W_META_SETTEXTCHAREXTRA:
            case W_META_SETTEXTJUSTIFICATION:
            case W_META_FLOODFILL:
            case W_META_FILLREGION:
            case W_META_FRAMEREGION:
            case W_META_INVERTREGION:
            case W_META_PAINTREGION:
            case W_META_DRAWTEXT:
            case W_META_SETMAPPERFLAGS:
            case W_META_SETDIBTODEV:
            case W_META_REALIZEPALETTE:
            case W_META_ANIMATEPALETTE:
            case W_META_SETPALENTRIES:
            case W_META_RESIZEPALETTE:
            case W_META_EXTFLOODFILL:
            case W_META_RESETDC:
            case W_META_STARTDOC:
            case W_META_STARTPAGE:
            case W_META_ENDPAGE:
            case W_META_ABORTDOC:
            case W_META_ENDDOC:
            {
                SAL_WARN("emfio", "TODO: WMF record not implemented: " << record_type_name(nFunc));
            }
            break;

            default:
            {
                SAL_WARN("emfio", "Unknown Meta Action: 0x" << std::hex << nFunc << std::dec);
            }
        }

        if (!bRecordOk)
        {
            SAL_WARN("emfio", "WMF validation failed, record: " <<
                              record_type_name(nFunc) << ", size: " << nRecordSize);
            mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR);
        }

        // tdf#127471
        maScaledFontHelper.applyAlternativeFontScale();
    }

    const tools::Long   aMaxWidth = 1024;

    bool WmfReader::ReadHeader()
    {
        sal_uInt64 const nStrmPos = mpInputStream->Tell();

        sal_uInt32 nPlaceableMetaKey(0);
        // if available read the METAFILEHEADER
        mpInputStream->ReadUInt32( nPlaceableMetaKey );
        if (!mpInputStream->good())
            return false;

        tools::Rectangle aPlaceableBound;

        mbPlaceable = nPlaceableMetaKey == 0x9ac6cdd7L;

        SAL_INFO("emfio", "Placeable: \"" << (mbPlaceable ? "yes" : "no") << "\"");

        if (mbPlaceable)
        {
            //TODO do some real error handling here
            sal_Int16 nVal(0);

            // Skip reserved bytes
            mpInputStream->SeekRel(2);

            // BoundRect
            // These are simply ignored for now
            mpInputStream->ReadInt16( nVal );
            aPlaceableBound.SetLeft( nVal );
            mpInputStream->ReadInt16( nVal );
            aPlaceableBound.SetTop( nVal );
            mpInputStream->ReadInt16( nVal );
            aPlaceableBound.SetRight( nVal );
            mpInputStream->ReadInt16( nVal );
            aPlaceableBound.SetBottom( nVal );

            // inch
            mpInputStream->ReadUInt16( mnUnitsPerInch );

            SAL_WARN_IF(!mnUnitsPerInch, "emfio", "Invalid WMF: mnUnitsPerInch is 0");

            // reserved
            mpInputStream->SeekRel( 4 );

            // Skip and don't check the checksum
            mpInputStream->SeekRel( 2 );

            // Skip wmf header
            mpInputStream->Seek( nStrmPos + 40 );    // set the streampos to the start of the metaactions
            GetPlaceableBound( aPlaceableBound, mpInputStream );
            // Go back to the place after placeable header
            mpInputStream->Seek( nStrmPos + 22);
        }
        else
        {
            // Default is 1440, but it is set to 96 to show the wmf larger
            mnUnitsPerInch = 96;

            if (mpExternalHeader != nullptr
                && mpExternalHeader->xExt > 0
                && mpExternalHeader->yExt > 0
                && (mpExternalHeader->mapMode == MappingMode::MM_ISOTROPIC || mpExternalHeader->mapMode == MappingMode::MM_ANISOTROPIC))
            {
                // #n417818#: If we have an external header then overwrite the bounds!
                tools::Rectangle aExtRect(0, 0,
                    o3tl::convert(mpExternalHeader->xExt, o3tl::Length::mm100, o3tl::Length::px),
                    o3tl::convert(mpExternalHeader->yExt, o3tl::Length::mm100, o3tl::Length::px));
                aPlaceableBound = aExtRect;

                SAL_INFO("emfio", "External header size "
                    " left: " << aPlaceableBound.Left() << " top: " << aPlaceableBound.Top()
                    << " right: " << aPlaceableBound.Right() << " bottom: " << aPlaceableBound.Bottom());

                SetMapMode(static_cast<MappingMode>(mpExternalHeader->mapMode));;
            }
            else
            {
                mpInputStream->Seek(nStrmPos + 18);    // set the streampos to the start of the metaactions
                GetPlaceableBound(aPlaceableBound, mpInputStream);

                // The image size is not known so normalize the calculated bounds so that the
                // resulting image is not too big
                if (aPlaceableBound.GetWidth() > aMaxWidth)
                {
                    const double fMaxWidth = static_cast<double>(aMaxWidth);
                    double fRatio = aPlaceableBound.GetWidth() / fMaxWidth;

                    // changing mnUnitsPerInch as a tool to scale wmf
                    mnUnitsPerInch *= fRatio;

                }
                SAL_INFO("emfio", "Placeable bounds "
                                  " left: " << aPlaceableBound.Left() << " top: " << aPlaceableBound.Top() <<
                                  " right: " << aPlaceableBound.Right() << " bottom: " << aPlaceableBound.Bottom());
            }

            mpInputStream->Seek( nStrmPos );
        }

        SetWinOrg( aPlaceableBound.TopLeft() );
        Size aWMFSize(
            std::abs( aPlaceableBound.GetWidth() ), std::abs( aPlaceableBound.GetHeight() ) );
        SetWinExt( aWMFSize );

        SAL_INFO("emfio", "WMF size  w: " << aWMFSize.Width()    << " h: " << aWMFSize.Height());

        Size aDevExt( 10000, 10000 );
        if( ( std::abs( aWMFSize.Width() ) > 1 ) && ( std::abs( aWMFSize.Height() ) > 1 ) && mnUnitsPerInch )
        {
            const double    fFrac = 1.0 / mnUnitsPerInch;
            MapMode         aWMFMap( MapUnit::MapInch, Point(), fFrac, fFrac );
            Size            aSize100(OutputDevice::LogicToLogic(aWMFSize, aWMFMap, MapMode(MapUnit::Map100thMM)));
            aDevExt = Size( std::abs( aSize100.Width() ), std::abs( aSize100.Height() ) );
        }
        SetDevExt( aDevExt );

        SAL_INFO("emfio", "Dev size  w: " << aDevExt.Width()    << " h: " << aDevExt.Height());

        // read the METAHEADER
        sal_uInt32 nMetaKey(0);
        mpInputStream->ReadUInt32( nMetaKey ); // type and headersize
        if (!mpInputStream->good())
            return false;
        if (nMetaKey != 0x00090001)
        {
            sal_uInt16 aNextWord(0);
            mpInputStream->ReadUInt16( aNextWord );
            if (nMetaKey != 0x10000 || aNextWord != 0x09)
            {
                mpInputStream->SetError( SVSTREAM_FILEFORMAT_ERROR );
                return false;
            }
        }

        mpInputStream->SeekRel( 2 ); // Version (of Windows)
        mpInputStream->SeekRel( 4 ); // Size (of file in words)
        mpInputStream->SeekRel( 2 ); // NoObjects (maximum number of simultaneous objects)
        mpInputStream->SeekRel( 4 ); // MaxRecord (size of largest record in words)
        mpInputStream->SeekRel( 2 ); // NoParameters (Unused

        return mpInputStream->good();
    }

    void WmfReader::ReadWMF()
    {
        sal_uInt16  nFunction;

        mnSkipActions = 0;

        mpEMFStream.reset();
        mnEMFRecCount = 0;
        mnEMFRec = 0;
        mnEMFSize = 0;

        SetMapMode( MappingMode::MM_ANISOTROPIC );
        SetWinOrg( Point() );
        SetWinExt( Size( 1, 1 ) );
        SetDevExt( Size( 10000, 10000 ) );

        mnEndPos=mpInputStream->TellEnd();
        mpInputStream->Seek( mnStartPos );

        if ( ReadHeader( ) )
        {
            auto nPos = mpInputStream->Tell();

            if( mnEndPos - mnStartPos )
            {
                bool bEMFAvailable = false;
                while( !mpInputStream->eof() )
                {
                    mpInputStream->ReadUInt32(mnRecSize).ReadUInt16( nFunction );

                    if (!mpInputStream->good() || (mnRecSize < 3) || (nFunction == W_META_EOF))
                    {
                        if (mpInputStream->eof())
                            mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR);

                        break;
                    }

                    const sal_uInt32 nAvailableBytes = mnEndPos - nPos;
                    const sal_uInt32 nMaxPossibleRecordSize = nAvailableBytes/2;
                    if (mnRecSize > nMaxPossibleRecordSize)
                    {
                        mpInputStream->SetError(SVSTREAM_FILEFORMAT_ERROR);
                        break;
                    }

                    if ( !bEMFAvailable )
                    {
                        if(   !maBmpSaveList.empty()
                          && ( nFunction != W_META_STRETCHDIB    )
                          && ( nFunction != W_META_DIBBITBLT     )
                          && ( nFunction != W_META_DIBSTRETCHBLT )
                          )
                        {
                            ResolveBitmapActions( maBmpSaveList );
                        }

                        if ( !mnSkipActions)
                            ReadRecordParams( mnRecSize, nFunction );
                        else
                            mnSkipActions--;

                        if(mpEMFStream && mnEMFRecCount == mnEMFRec)
                        {
                            GDIMetaFile aMeta;
                            SvMemoryStream aStream(mpEMFStream->data(), mpEMFStream->size(), StreamMode::STD_READ);
                            std::unique_ptr<EmfReader> pEMFReader(std::make_unique<EmfReader>(aStream, aMeta));
                            pEMFReader->SetEnableEMFPlus(mbEnableEMFPlus);
                            bEMFAvailable = pEMFReader->ReadEnhWMF();
                            pEMFReader.reset(); // destroy first!!!

                            if( bEMFAvailable )
                            {
                                AddFromGDIMetaFile( aMeta );
                                SetrclFrame( tools::Rectangle( Point(0, 0), aMeta.GetPrefSize()));

                                // the stream needs to be set to the wmf end position,
                                // otherwise the GfxLink that is created will be incorrect
                                // (leading to graphic loss after swapout/swapin).
                                // so we will proceed normally, but are ignoring further wmf
                                // records
                            }
                            else
                            {
                                // something went wrong
                                // continue with WMF, don't try this again
                                mpEMFStream.reset();
                            }
                        }
                    }

                    nPos += mnRecSize * 2;
                    mpInputStream->Seek(nPos);
                }
            }
            else
                mpInputStream->SetError( SVSTREAM_GENERALERROR );

            if( !mpInputStream->GetError() && !maBmpSaveList.empty() )
                ResolveBitmapActions( maBmpSaveList );
        }
        if ( mpInputStream->GetError() )
            mpInputStream->Seek( mnStartPos );
    }

    void WmfReader::GetPlaceableBound( tools::Rectangle& rPlaceableBound, SvStream* pStm )
    {
        bool bRet = true;

        tools::Rectangle aBound;
        aBound.SetLeft( RECT_MAX );
        aBound.SetTop( RECT_MAX );
        aBound.SetRight( RECT_MIN );
        aBound.SetBottom( RECT_MIN );
        bool bBoundsDetermined = false;

        auto nPos = pStm->Tell();
        auto nEnd = nPos + pStm->remainingSize();

        Point aWinOrg(0,0);
        std::optional<Size>  aWinExt;

        Point aViewportOrg(0,0);
        std::optional<Size>  aViewportExt;

        MappingMode eMapMode = MappingMode::MM_ANISOTROPIC;

        if (nEnd - nPos)
        {
            sal_uInt16 nFunction;
            sal_uInt32 nRSize;

            while( bRet )
            {
                pStm->ReadUInt32( nRSize ).ReadUInt16( nFunction );

                if( pStm->GetError() )
                {
                    bRet = false;
                    break;
                }
                else if (pStm->eof() || nRSize < 3)
                {
                    pStm->SetError( SVSTREAM_FILEFORMAT_ERROR );
                    bRet = false;
                    break;
                }
                else if (nFunction == W_META_EOF)
                {
                    break;
                }

                switch( nFunction )
                {
                    case W_META_SETWINDOWORG:
                    {
                        aWinOrg = ReadYX();
                    }
                    break;

                    case W_META_SETWINDOWEXT:
                    {
                        sal_Int16 nWidth(0), nHeight(0);
                        pStm->ReadInt16(nHeight);
                        pStm->ReadInt16(nWidth);
                        aWinExt = Size(nWidth, nHeight);
                    }
                    break;

                    case W_META_SETVIEWPORTORG:
                    {
                        aViewportOrg = ReadYX();
                    }
                    break;

                    case W_META_SETVIEWPORTEXT:
                    {
                        sal_Int16 nWidth(0), nHeight(0);
                        pStm->ReadInt16(nHeight);
                        pStm->ReadInt16(nWidth);
                        aViewportExt = Size(nWidth, nHeight);
                    }
                    break;

                    case W_META_SETMAPMODE:
                    {
                        sal_uInt16 nMapMode(0);
                        pStm->ReadUInt16(nMapMode);
                        eMapMode = static_cast<MappingMode>(nMapMode);
                    }
                    break;

                    case W_META_MOVETO:
                    case W_META_LINETO:
                    {
                        GetWinExtMax( ReadYX(), aBound, eMapMode );
                        bBoundsDetermined = true;
                    }
                    break;

                    case W_META_RECTANGLE:
                    case W_META_INTERSECTCLIPRECT:
                    case W_META_EXCLUDECLIPRECT:
                    case W_META_ELLIPSE:
                    {
                        GetWinExtMax( ReadRectangle(), aBound, eMapMode );
                        bBoundsDetermined = true;
                    }
                    break;

                    case W_META_ROUNDRECT:
                    {
                        ReadYXExt(); // size
                        GetWinExtMax( ReadRectangle(), aBound, eMapMode );
                        bBoundsDetermined = true;
                    }
                    break;

                    case W_META_ARC:
                    case W_META_PIE:
                    case W_META_CHORD:
                    {
                        ReadYX(); // end
                        ReadYX(); // start
                        GetWinExtMax( ReadRectangle(), aBound, eMapMode );
                        bBoundsDetermined = true;
                    }
                    break;

                    case W_META_POLYGON:
                    {
                        bool bRecordOk = true;

                        sal_uInt16 nPoints(0);
                        pStm->ReadUInt16( nPoints );

                        if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16)))
                        {
                            bRecordOk = false;
                        }
                        else
                        {
                            for(sal_uInt16 i = 0; i < nPoints; i++ )
                            {
                                GetWinExtMax( ReadPoint(), aBound, eMapMode );
                                bBoundsDetermined = true;
                            }
                        }

                        bRecordOk &= pStm->good();

                        SAL_WARN_IF(!bRecordOk, "emfio", "polyline record claimed more points than the stream can provide");

                        if (!bRecordOk)
                        {
                            pStm->SetError( SVSTREAM_FILEFORMAT_ERROR );
                            bRet = false;
                            break;
                        }
                    }
                    break;

                    case W_META_POLYPOLYGON:
                    {
                        bool bRecordOk = true;
                        sal_uInt16 nPoly(0), nPoints(0);
                        pStm->ReadUInt16(nPoly);
                        if (nPoly > pStm->remainingSize() / sizeof(sal_uInt16))
                        {
                            bRecordOk = false;
                        }
                        else
                        {
                            for(sal_uInt16 i = 0; i < nPoly; i++ )
                            {
                                sal_uInt16 nP = 0;
                                pStm->ReadUInt16( nP );
                                if (nP > SAL_MAX_UINT16 - nPoints)
                                {
                                    bRecordOk = false;
                                    break;
                                }
                                nPoints += nP;
                            }
                        }

                        SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record has more polygons than we can handle");

                        bRecordOk &= pStm->good();

                        if (!bRecordOk)
                        {
                            pStm->SetError( SVSTREAM_FILEFORMAT_ERROR );
                            bRet = false;
                            break;
                        }

                        if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16)))
                        {
                            bRecordOk = false;
                        }
                        else
                        {
                            for (sal_uInt16 i = 0; i < nPoints; i++ )
                            {
                                GetWinExtMax( ReadPoint(), aBound, eMapMode );
                                bBoundsDetermined = true;
                            }
                        }

                        SAL_WARN_IF(!bRecordOk, "emfio", "polypolygon record claimed more points than the stream can provide");

                        bRecordOk &= pStm->good();

                        if (!bRecordOk)
                        {
                            pStm->SetError( SVSTREAM_FILEFORMAT_ERROR );
                            bRet = false;
                            break;
                        }
                    }
                    break;

                    case W_META_POLYLINE:
                    {
                        bool bRecordOk = true;

                        sal_uInt16 nPoints(0);
                        pStm->ReadUInt16(nPoints);
                        if (nPoints > pStm->remainingSize() / (2 * sizeof(sal_uInt16)))
                        {
                            bRecordOk = false;
                        }
                        else
                        {
                            for (sal_uInt16 i = 0; i < nPoints; ++i)
                            {
                                GetWinExtMax( ReadPoint(), aBound, eMapMode );
                                bBoundsDetermined = true;
                            }
                        }

                        SAL_WARN_IF(!bRecordOk, "emfio", "polyline record claimed more points than the stream can provide");

                        bRecordOk &= pStm->good();

                        if (!bRecordOk)
                        {
                            pStm->SetError( SVSTREAM_FILEFORMAT_ERROR );
                            bRet = false;
                            break;
                        }
                    }
                    break;

                    case W_META_SETPIXEL:
                    {
                        ReadColor();
                        GetWinExtMax( ReadYX(), aBound, eMapMode );
                        bBoundsDetermined = true;
                    }
                    break;

                    case W_META_TEXTOUT:
                    {
                        sal_uInt16 nLength(0);
                        pStm->ReadUInt16( nLength );
                        // todo: we also have to take care of the text width
                        if ( nLength )
                        {
                            pStm->SeekRel( ( nLength + 1 ) &~ 1 );
                            GetWinExtMax( ReadYX(), aBound, eMapMode );
                            bBoundsDetermined = true;
                        }
                    }
                    break;

                    case W_META_EXTTEXTOUT:
                    {
                        sal_uInt16 nLen(0), nOptions;
                        Point aPosition = ReadYX();
                        pStm->ReadUInt16( nLen ).ReadUInt16( nOptions );
                        // todo: we also have to take care of the text width
                        if( nLen )
                        {
                            GetWinExtMax( aPosition, aBound, eMapMode );
                            bBoundsDetermined = true;
                        }
                    }
                    break;
                    case W_META_BITBLT:
                    case W_META_DIBBITBLT:
                    case W_META_DIBSTRETCHBLT:
                    case W_META_STRETCHBLT:
                    case W_META_STRETCHDIB:
                    {
                        sal_uInt32 nRasterOperation;
                        sal_Int16 nYSrc, nXSrc;
                        sal_uInt16 nColorUsage;
                        pStm->ReadUInt32( nRasterOperation );

                        if( nFunction == W_META_STRETCHDIB )
                            pStm->ReadUInt16( nColorUsage );

                        if( nFunction == W_META_DIBSTRETCHBLT ||
                            nFunction == W_META_STRETCHBLT ||
                            nFunction == W_META_STRETCHDIB )
                        {
                            sal_Int16 nSrcHeight, nSrcWidth;
                            pStm->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth );
                        }

                        // nYSrc and nXSrc is the offset of the first pixel
                        pStm->ReadInt16( nYSrc ).ReadInt16( nXSrc );

                        const bool bNoSourceBitmap = ( nFunction != W_META_STRETCHDIB ) && ( nRSize == ( ( static_cast< sal_uInt32 >( nFunction ) >> 8 ) + 3 ) );
                        if ( bNoSourceBitmap )
                            mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored)

                        Size aDestSize( ReadYXExt() );
                        if ( aDestSize.Width() && aDestSize.Height() )  // #92623# do not try to read buggy bitmaps
                        {
                            tools::Rectangle aDestRect( ReadYX(), aDestSize );
                            GetWinExtMax( aDestRect, aBound, eMapMode );
                            bBoundsDetermined = true;
                        }
                    }
                    break;

                    case W_META_PATBLT:
                    {
                        sal_uInt32 nROP(0);
                        pStm->ReadUInt32( nROP );
                        Size aSize = ReadYXExt();
                        GetWinExtMax( tools::Rectangle( ReadYX(), aSize ), aBound, eMapMode );
                        bBoundsDetermined = true;
                    }
                    break;
                }

                const auto nAvailableBytes = nEnd - nPos;
                const auto nMaxPossibleRecordSize = nAvailableBytes/2;
                if (nRSize <= nMaxPossibleRecordSize)
                {
                    nPos += nRSize * 2;
                    pStm->Seek(nPos);
                }
                else
                {
                    pStm->SetError( SVSTREAM_FILEFORMAT_ERROR );
                    bRet = false;
                }
            }
        }
        else
        {
            pStm->SetError( SVSTREAM_GENERALERROR );
            bRet = false;
        }
        if (!bRet)
        {
            SAL_WARN("emfio", "Unable to calculate Placeable Bounds");
            return;
        }

        if (aWinExt)
        {
            rPlaceableBound = tools::Rectangle(aWinOrg, *aWinExt);
            if (mbPlaceable && eMapMode == MM_ANISOTROPIC)
            {
                // It seems that (in MM_ANISOTROPIC WMFs) the "inch" field (PPI) in META_PLACEABLE is
                // ignored and instead competitor office suites decide what it should be arbitrarily
                // Could have to do with MM_ANISOTROPICs definition:
                // Logical units are mapped to arbitrary units with arbitrarily scaled axes.
                // The issue is that when PPI is bigger than the window size, the image appears
                // tiny (smaller than an inch squared).
                // A solution is to scale PPI down in such images to an arbitrary amount that makes
                // the image visible:
                auto nWidth = rPlaceableBound.GetWidth();
                auto nHeight = rPlaceableBound.GetHeight();
                if (mnUnitsPerInch > nWidth && mnUnitsPerInch > nHeight)
                    mnUnitsPerInch = std::max(nWidth, nHeight);
            }
            SAL_INFO("emfio", "Window dimension "
                       " left: " << rPlaceableBound.Left()  << " top: " << rPlaceableBound.Top()
                    << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom());
        }
        else if (aViewportExt)
        {
            rPlaceableBound = tools::Rectangle(aViewportOrg, *aViewportExt);
            SAL_INFO("emfio", "Viewport dimension "
                       " left: " << rPlaceableBound.Left()  << " top: " << rPlaceableBound.Top()
                    << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom());
        }
        else if (bBoundsDetermined)
        {
            rPlaceableBound = aBound;
            SAL_INFO("emfio", "Determined dimension "
                       " left: " << rPlaceableBound.Left()  << " top: " << rPlaceableBound.Top()
                    << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom());
        }
        else
        {
            rPlaceableBound.SetLeft( 0 );
            rPlaceableBound.SetTop( 0 );
            rPlaceableBound.SetRight( aMaxWidth );
            rPlaceableBound.SetBottom( aMaxWidth );
            SAL_INFO("emfio", "Default dimension "
                       " left: " << rPlaceableBound.Left()  << " top: " << rPlaceableBound.Top()
                    << " right: " << rPlaceableBound.Right() << " bottom: " << rPlaceableBound.Bottom());
        }
    }

    WmfReader::WmfReader(SvStream& rStreamWMF, GDIMetaFile& rGDIMetaFile, const WmfExternal* pExternalHeader)
        : MtfTools(rGDIMetaFile, rStreamWMF)
        , mnUnitsPerInch(96)
        , mnRecSize(0)
        , mbPlaceable(false)
        , mnEMFRecCount(0)
        , mnEMFRec(0)
        , mnEMFSize(0)
        , mnSkipActions(0)
        , mpExternalHeader(pExternalHeader)
    {
    }
}

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