/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */

#include <osl/thread.h>
#include <o3tl/safeint.hxx>
#include <tools/poly.hxx>
#include <tools/fract.hxx>
#include <tools/stream.hxx>
#include <sal/log.hxx>
#include <vcl/filter/ImportOutput.hxx>
#include <vcl/dibtools.hxx>
#include <vcl/virdev.hxx>
#include <vcl/lineinfo.hxx>
#include <vcl/gdimtf.hxx>
#include <filter/MetReader.hxx>
#include <basegfx/numeric/ftools.hxx>

#include <cmath>
#include <memory>

class FilterConfigItem;

namespace {

enum PenStyle { PEN_NULL, PEN_SOLID, PEN_DOT, PEN_DASH, PEN_DASHDOT };

}

// -----------------------------Field Types-------------------------------

#define BegDocumnMagic 0xA8A8 /* Begin Document */
#define EndDocumnMagic 0xA8A9 /* End Document   */

#define BegResGrpMagic 0xC6A8 /* Begin Resource Group */
#define EndResGrpMagic 0xC6A9 /* End Resource Group   */

#define BegColAtrMagic 0x77A8 /* Begin Color Attribute Table */
#define EndColAtrMagic 0x77A9 /* End Color Attribute Table   */
#define BlkColAtrMagic 0x77B0 /* Color Attribute Table       */
#define MapColAtrMagic 0x77AB /* Map Color Attribute Table   */

#define BegImgObjMagic 0xFBA8 /* Begin Image Object    */
#define EndImgObjMagic 0xFBA9 /* End Image Object      */
#define DscImgObjMagic 0xFBA6 /* Image Data Descriptor */
#define DatImgObjMagic 0xFBEE /* Image Picture Data    */

#define BegObEnv1Magic 0xC7A8 /* Begin Object Environment Group */
#define EndObEnv1Magic 0xC7A9 /* End Object Environment Group   */

#define BegGrfObjMagic 0xBBA8 /* Begin Graphics Object    */
#define EndGrfObjMagic 0xBBA9 /* End Graphics Object      */
#define DscGrfObjMagic 0xBBA6 /* Graphics Data Descriptor */
#define DatGrfObjMagic 0xBBEE /* Graphics Data            */

#define MapCodFntMagic 0x8AAB /* Map Coded Font    */
#define MapDatResMagic 0xC3AB /* Map Data Resource */

// -----------------------------Order Types-------------------------------

#define GOrdGivArc 0xC6   /* 1 Arc at given position   */
#define GOrdCurArc 0x86   /* 1 Arc at current position */
#define GOrdGivBzr 0xE5   /* 1 Beziercurve at given position   */
#define GOrdCurBzr 0xA5   /* 1 Beziercurve at current position */
#define GOrdGivBox 0xC0   /* 1 Box at given position   */
#define GOrdCurBox 0x80   /* 1 Box at current position */
#define GOrdGivFil 0xC5   /* 1 Fillet at given position   */
#define GOrdCurFil 0x85   /* 1 Fillet at current position */
#define GOrdGivCrc 0xC7   /* 1 Full arc (circle) at given position   */
#define GOrdCurCrc 0x87   /* 1 Full arc (circle) at current position */
#define GOrdGivLin 0xC1   /* 1 Line at given position   */
#define GOrdCurLin 0x81   /* 1 Line at current position */
#define GOrdGivMrk 0xC2   /* 1 Marker at given position   */
#define GOrdCurMrk 0x82   /* 1 Marker at current position */
#define GOrdGivArP 0xE3   /* 1 Partial arc at given position   */
#define GOrdCurArP 0xA3   /* 1 Partial arc at current position */
#define GOrdGivRLn 0xE1   /* 1 Relative line at given position   */
#define GOrdCurRLn 0xA1   /* 1 Relative line at current position */
#define GOrdGivSFl 0xE4   /* 1 Sharp fillet at given position   */
#define GOrdCurSFl 0xA4   /* 1 Sharp fillet at current position */

#define GOrdGivStM 0xF1   /* 1 Character string move at given position   */
#define GOrdCurStM 0xB1   /* 1 Character string move at current position */
#define GOrdGivStr 0xC3   /* 1 Character string at given position   */
#define GOrdCurStr 0x83   /* 1 Character string at current position */
#define GOrdGivStx 0xFEF0 /* 2 Character string extended at given position   */
#define GOrdCurStx 0xFEB0 /* 2 Character string extended at current position */

#define GOrdGivImg 0xD1   /* 1 Begin Image at given position   */
#define GOrdCurImg 0x91   /* 1 Begin Image at current position */
#define GOrdImgDat 0x92   /* 1 Image data                      */
#define GOrdEndImg 0x93   /* 1 End Image                       */
#define GOrdBegAra 0x68   /* 0 1 Begin area */
#define GOrdEndAra 0x60   /* 1 End area     */
#define GOrdBegElm 0xD2   /* 1 Begin element */
#define GOrdEndElm 0x49   /* 0 1 End element */

#define GOrdBegPth 0xD0   /* 1 Begin path    */
#define GOrdEndPth 0x7F   /* 0 1 End path    */
#define GOrdFilPth 0xD7   /* 1 Fill path     */
#define GOrdModPth 0xD8   /* 1 Modify path   */
#define GOrdOutPth 0xD4   /* 1 Outline path  */
#define GOrdSClPth 0xB4   /* 1 Set clip path */

#define GOrdNopNop 0x00   /* 0 0 No operation */
#define GOrdRemark 0x01   /* 1 Comment */
#define GOrdSegLab 0xD3   /* 1 Label */
#define GOrdBitBlt 0xD6   /* 1 Bitblt */
#define GOrdCalSeg 0x07   /* 1 Call Segment */
#define GOrdSSgBnd 0x32   /* 1 Set segment boundary */
#define GOrdSegChr 0x04   /* 1 Segment characteristics */
#define GOrdCloFig 0x7D   /* 0 1 Close Figure */
#define GOrdEndSym 0xFF   /* 0 0 End of symbol definition */
#define GOrdEndPlg 0x3E   /* 0 1 End prolog */
#define GOrdEscape 0xD5   /* 1 Escape */
#define GOrdExtEsc 0xFED5 /* 2 Extended Escape */
#define GOrdPolygn 0xF3   /* 2 Polygons */

#define GOrdStkPop 0x3F   /* 0 1 Pop */

#define GOrdSIvAtr 0x14   /* 1 Set individual attribute          */
#define GOrdPIvAtr 0x54   /* 1 Push and set individual attribute */
#define GOrdSColor 0x0A   /* 0 1 Set color          */
#define GOrdPColor 0x4A   /* 0 1 Push and set color */
#define GOrdSIxCol 0xA6   /* 1 Set indexed color          */
#define GOrdPIxCol 0xE6   /* 1 Push and set indexed color */
#define GOrdSXtCol 0x26   /* 1 Set extended color          */
#define GOrdPXtCol 0x66   /* 1 Push and set extended color */
#define GOrdSBgCol 0x25   /* 1 Set background color          */
#define GOrdPBgCol 0x65   /* 1 Push and set background color */
#define GOrdSBxCol 0xA7   /* 1 Set background indexed color          */
#define GOrdPBxCol 0xE7   /* 1 Push and set background indexed color */
#define GOrdSMixMd 0x0C   /* 0 1 Set mix          */
#define GOrdPMixMd 0x4C   /* 0 1 Push and set mix */
#define GOrdSBgMix 0x0D   /* 0 1 Set background mix          */
#define GOrdPBgMix 0x4D   /* 0 1 Push and set background mix */

#define GOrdSPtSet 0x08   /* 0 1 Set pattern set          */
#define GOrdPPtSet 0x48   /* 0 1 Push and set pattern set */
#define GOrdSPtSym 0x28   /* 0 1 Set pattern symbol          */
#define GOrdPPtSym 0x09   /* 0 1 Push and set pattern symbol */
#define GOrdSPtRef 0xA0   /* 1 Set model pattern reference          */
#define GOrdPPtRef 0xE0   /* 1 Push and set pattern reference point */

#define GOrdSLnEnd 0x1A   /* 0 1 Set line end          */
#define GOrdPLnEnd 0x5A   /* 0 1 Push and set line end */
#define GOrdSLnJoi 0x1B   /* 0 1 Set line join          */
#define GOrdPLnJoi 0x5B   /* 0 1 Push and set line join */
#define GOrdSLnTyp 0x18   /* 0 1 Set line type          */
#define GOrdPLnTyp 0x58   /* 0 1 Push and set line type */
#define GOrdSLnWdt 0x19   /* 0 1 Set line width          */
#define GOrdPLnWdt 0x59   /* 0 1 Push and set line width */
#define GOrdSFrLWd 0x11   /* 1 Set fractional line width          */
#define GOrdPFrLWd 0x51   /* 1 Push and set fractional line width */
#define GOrdSStLWd 0x15   /* 1 Set stroke line width          */
#define GOrdPStLWd 0x55   /* 1 Push and set stroke line width */

#define GOrdSChDir 0x3A   /* 0 1 Set character direction          */
#define GOrdPChDir 0x7A   /* 0 1 Push and set character direction */
#define GOrdSChPrc 0x39   /* 0 1 Set character precision          */
#define GOrdPChPrc 0x79   /* 0 1 Push and set character precision */
#define GOrdSChSet 0x38   /* 0 1 Set character set          */
#define GOrdPChSet 0x78   /* 0 1 Push and set character set */
#define GOrdSChAng 0x34   /* 1 Set character angle          */
#define GOrdPChAng 0x74   /* 1 Push and set character angle */
#define GOrdSChBrx 0x05   /* 1 Set character break extra          */
#define GOrdPChBrx 0x45   /* 1 Push and set character break extra */
#define GOrdSChCel 0x33   /* 1 Set character cell          */
#define GOrdPChCel 0x03   /* 1 Push and set character cell */
#define GOrdSChXtr 0x17   /* 1 Set character extra          */
#define GOrdPChXtr 0x57   /* 1 Push and set character extra */
#define GOrdSChShr 0x35   /* 1 Set character shear          */
#define GOrdPChShr 0x75   /* 1 Push and set character shear */
#define GOrdSTxAlg 0x36   /* 0 2 Set text allingment          */
#define GOrdPTxAlg 0x76   /* 0 2 Push and set text allingment */

#define GOrdSMkPrc 0x3B   /* 0 1 Set marker precision          */
#define GOrdPMkPrc 0x7B   /* 0 1 Push and set marker precision */
#define GOrdSMkSet 0x3C   /* 0 1 Set marker set          */
#define GOrdPMkSet 0x7C   /* 0 1 Push and set marker set */
#define GOrdSMkSym 0x29   /* 0 1 Set marker symbol          */
#define GOrdPMkSym 0x69   /* 0 1 Push and set marker symbol */
#define GOrdSMkCel 0x37   /* 1 Set marker cell          */
#define GOrdPMkCel 0x77   /* 1 Push and set marker cell */

#define GOrdSArcPa 0x22   /* 1 Set arc parameters          */
#define GOrdPArcPa 0x62   /* 1 Push and set arc parameters */

#define GOrdSCrPos 0x21   /* 1 Set current position          */
#define GOrdPCrPos 0x61   /* 1 Push and set current position */

#define GOrdSMdTrn 0x24   /* 1 Set model transform          */
#define GOrdPMdTrn 0x64   /* 1 Push and set model transform */
#define GOrdSPkIdn 0x43   /* 1 Set pick identifier          */
#define GOrdPPkIdn 0x23   /* 1 Push and set pick identifier */
#define GOrdSVwTrn 0x31   /* 1 Set viewing transform */
#define GOrdSVwWin 0x27   /* 1 Set viewing window          */
#define GOrdPVwWin 0x67   /* 1 Push and set viewing window */

constexpr double tau = 2.0 * M_PI;

//============================ OS2METReader ==================================

namespace {

struct OSPalette {
    OSPalette * pSucc;
    sal_uInt32 * p0RGB; // May be NULL!
    size_t nSize;
};

struct OSArea {
    OSArea    * pSucc;
    sal_uInt8   nFlags;
    tools::PolyPolygon aPPoly;
    bool    bClosed;
    Color       aCol;
    Color       aBgCol;
    RasterOp    eMix;
    RasterOp    eBgMix;
    bool    bFill;

    OSArea()
        : pSucc(nullptr)
        , nFlags(0)
        , bClosed(false)
        , eMix(RasterOp::OverPaint)
        , eBgMix(RasterOp::OverPaint)
        , bFill(false)
    {
    }
};

struct OSPath
{
    OSPath*     pSucc;
    sal_uInt32  nID;
    tools::PolyPolygon aPPoly;
    bool    bClosed;
    bool    bStroke;

    OSPath()
        : pSucc(nullptr)
        , nID(0)
        , bClosed(false)
        , bStroke(false)
    {
    }
};

struct OSFont {
    OSFont *  pSucc;
    sal_uInt32 nID;
    vcl::Font aFont;

    OSFont()
        : pSucc(nullptr)
        , nID(0)
    {
    }
};

struct OSBitmap {
    OSBitmap * pSucc;
    sal_uInt32 nID;
    Bitmap   aBitmap;

    // required during reading of the bitmap:
    SvStream * pBMP; // pointer to temporary Windows-BMP file or NULL
    sal_uInt32 nWidth, nHeight;
    sal_uInt16 nBitsPerPixel;
    sal_uInt32 nMapPos;
};

struct OSAttr
{
    OSAttr *   pSucc;
    sal_uInt16 nPushOrder;
    sal_uInt8  nIvAttrA, nIvAttrP; // special variables for the Order "GOrdPIvAtr"

    Color    aLinCol;
    Color    aLinBgCol;
    RasterOp eLinMix;
    RasterOp eLinBgMix;
    Color    aChrCol;
    Color    aChrBgCol;
    RasterOp eChrMix;
    RasterOp eChrBgMix;
    Color    aMrkCol;
    Color    aMrkBgCol;
    RasterOp eMrkMix;
    RasterOp eMrkBgMix;
    Color    aPatCol;
    Color    aPatBgCol;
    RasterOp ePatMix;
    RasterOp ePatBgMix;
    Color    aImgCol;
    Color    aImgBgCol;
    RasterOp eImgMix;
    RasterOp eImgBgMix;
    sal_Int32 nArcP, nArcQ, nArcR, nArcS;
    Degree10  nChrAng;
    sal_Int32 nChrCellHeight;
    sal_uInt32 nChrSet;
    Point    aCurPos;
    PenStyle eLinStyle;
    sal_uInt16   nLinWidth;
    Size     aMrkCellSize;
    sal_uInt8     nMrkPrec;
    sal_uInt8     nMrkSet;
    sal_uInt8     nMrkSymbol;
    bool     bFill;
    sal_uInt16   nStrLinWidth;

    OSAttr()
        : pSucc(nullptr)
        , nPushOrder(0)
        , nIvAttrA(0)
        , nIvAttrP(0)
        , eLinMix(RasterOp::OverPaint)
        , eLinBgMix(RasterOp::OverPaint)
        , eChrMix(RasterOp::OverPaint)
        , eChrBgMix(RasterOp::OverPaint)
        , eMrkMix(RasterOp::OverPaint)
        , eMrkBgMix(RasterOp::OverPaint)
        , ePatMix(RasterOp::OverPaint)
        , ePatBgMix(RasterOp::OverPaint)
        , eImgMix(RasterOp::OverPaint)
        , eImgBgMix(RasterOp::OverPaint)
        , nArcP(0)
        , nArcQ(0)
        , nArcR(0)
        , nArcS(0)
        , nChrAng(0)
        , nChrCellHeight(0)
        , nChrSet(0)
        , eLinStyle(PEN_NULL)
        , nLinWidth(0)
        , nMrkPrec(0)
        , nMrkSet(0)
        , nMrkSymbol(0)
        , bFill(false)
        , nStrLinWidth(0)
    {
    }
};

class OS2METReader {

private:

    int ErrorCode;

    SvStream      * pOS2MET;             // the OS2MET file to be read
    VclPtr<VirtualDevice> pVirDev;       // here the drawing methods are being called
                                         // While doing this a recording in the GDIMetaFile
                                         // will take place.
    tools::Rectangle       aBoundingRect;       // bounding rectangle as stored in the file
    tools::Rectangle       aCalcBndRect;        // bounding rectangle calculated on our own
    MapMode         aGlobMapMode;        // resolution of the picture
    bool        bCoord32;

    OSPalette  * pPaletteStack;

    LineInfo aLineInfo;

    OSArea   * pAreaStack; // Areas that are being worked on

    OSPath   * pPathStack; // Paths that are being worked on
    OSPath   * pPathList;  // finished Paths

    OSFont   * pFontList;

    OSBitmap * pBitmapList;

    OSAttr   aDefAttr;
    OSAttr   aAttr;
    OSAttr   * pAttrStack;

    std::unique_ptr<SvMemoryStream> xOrdFile;

    void AddPointsToPath(const tools::Polygon & rPoly);
    void AddPointsToArea(const tools::Polygon & rPoly);
    void CloseFigure();
    void PushAttr(sal_uInt16 nPushOrder);
    void PopAttr();

    void ChangeBrush( const Color& rPatColor, bool bFill );
    void SetPen( const Color& rColor, sal_uInt16 nStrLinWidth = 0, PenStyle ePenStyle = PEN_SOLID );
    void SetRasterOp(RasterOp eROP);

    void SetPalette0RGB(sal_uInt16 nIndex, sal_uInt32 nCol);
    sal_uInt32 GetPalette0RGB(sal_uInt32 nIndex) const;
        // gets color from palette, or, if it doesn't exist,
        // interprets nIndex as immediate RGB value.
    Color GetPaletteColor(sal_uInt32 nIndex) const;


    bool        IsLineInfo() const;
    void        DrawPolyLine( const tools::Polygon& rPolygon );
    void        DrawPolygon( const tools::Polygon& rPolygon );
    void        DrawPolyPolygon( const tools::PolyPolygon& rPolygon );
    sal_uInt16  ReadBigEndianWord();
    sal_uInt32  ReadBigEndian3BytesLong();
    sal_uInt32  ReadLittleEndian3BytesLong();
    sal_Int32   ReadCoord(bool b32);
    Point       ReadPoint( const bool bAdjustBoundRect = true );
    static RasterOp    OS2MixToRasterOp(sal_uInt8 nMix);
    void        ReadLine(bool bGivenPos, sal_uInt16 nOrderLen);
    void        ReadRelLine(bool bGivenPos, sal_uInt16 nOrderLen);
    void        ReadBox(bool bGivenPos);
    void        ReadBitBlt();
    void        ReadChrStr(bool bGivenPos, bool bMove, bool bExtra, sal_uInt16 nOrderLen);
    void        ReadArc(bool bGivenPos);
    void        ReadFullArc(bool bGivenPos, sal_uInt16 nOrderSize);
    void        ReadPartialArc(bool bGivenPos, sal_uInt16 nOrderSize);
    void        ReadPolygons();
    void        ReadBezier(bool bGivenPos, sal_uInt16 nOrderLen);
    void        ReadFillet(bool bGivenPos, sal_uInt16 nOrderLen);
    void        ReadFilletSharp(bool bGivenPos, sal_uInt16 nOrderLen);
    void        ReadMarker(bool bGivenPos, sal_uInt16 nOrderLen);
    void        ReadOrder(sal_uInt16 nOrderID, sal_uInt16 nOrderLen);
    void        ReadDsc(sal_uInt16 nDscID);
    void        ReadImageData(sal_uInt16 nDataID, sal_uInt16 nDataLen);
    void        ReadFont(sal_uInt16 nFieldSize);
    void        ReadField(sal_uInt16 nFieldType, sal_uInt16 nFieldSize);

public:

    OS2METReader();
    ~OS2METReader();

    void ReadOS2MET( SvStream & rStreamOS2MET, GDIMetaFile & rGDIMetaFile );
        // Reads from the stream an OS2MET file and fills up the GDIMetaFile

};

}

//=================== Methods of OS2METReader ==============================

OS2METReader::OS2METReader()
    : ErrorCode(0)
    , pOS2MET(nullptr)
    , pVirDev(VclPtr<VirtualDevice>::Create())
    , bCoord32(false)
    , pPaletteStack(nullptr)
    , pAreaStack(nullptr)
    , pPathStack(nullptr)
    , pPathList(nullptr)
    , pFontList(nullptr)
    , pBitmapList(nullptr)
    , pAttrStack(nullptr)
{
    pVirDev->EnableOutput(false);
}

OS2METReader::~OS2METReader()
{
    pVirDev.disposeAndClear();

    while (pAreaStack!=nullptr) {
        OSArea * p=pAreaStack;
        pAreaStack=p->pSucc;
        delete p;
    }

    while (pPathStack!=nullptr) {
        OSPath * p=pPathStack;
        pPathStack=p->pSucc;
        delete p;
    }

    while (pPathList!=nullptr) {
        OSPath * p=pPathList;
        pPathList=p->pSucc;
        delete p;
    }

    while (pFontList!=nullptr) {
        OSFont * p=pFontList;
        pFontList=p->pSucc;
        delete p;
    }

    while (pBitmapList!=nullptr) {
        OSBitmap * p=pBitmapList;
        pBitmapList=p->pSucc;
        delete p->pBMP;
        delete p;
    }

    while (pAttrStack!=nullptr) {
        OSAttr * p=pAttrStack;
        pAttrStack=p->pSucc;
        delete p;
    }

    while (pPaletteStack!=nullptr) {
        OSPalette * p=pPaletteStack;
        pPaletteStack=p->pSucc;
        delete[] p->p0RGB;
        delete p;
    }
}

bool OS2METReader::IsLineInfo() const
{
    return ( ! ( aLineInfo.IsDefault() || ( aLineInfo.GetStyle() == LineStyle::NONE ) || ( pVirDev->GetLineColor() == COL_TRANSPARENT ) ) );
}

void OS2METReader::DrawPolyLine( const tools::Polygon& rPolygon )
{
    if ( aLineInfo.GetStyle() == LineStyle::Dash || ( aLineInfo.GetWidth() > 1 ) )
        pVirDev->DrawPolyLine( rPolygon, aLineInfo );
    else
        pVirDev->DrawPolyLine( rPolygon );
}

void OS2METReader::DrawPolygon( const tools::Polygon& rPolygon )
{
    if ( IsLineInfo() )
    {
        pVirDev->Push( vcl::PushFlags::LINECOLOR );
        pVirDev->SetLineColor( COL_TRANSPARENT );
        pVirDev->DrawPolygon( rPolygon );
        pVirDev->Pop();
        pVirDev->DrawPolyLine( rPolygon, aLineInfo );
    }
    else
        pVirDev->DrawPolygon( rPolygon );
}

void OS2METReader::DrawPolyPolygon( const tools::PolyPolygon& rPolyPolygon )
{
    if ( IsLineInfo() )
    {
        pVirDev->Push( vcl::PushFlags::LINECOLOR );
        pVirDev->SetLineColor( COL_TRANSPARENT );
        pVirDev->DrawPolyPolygon( rPolyPolygon );
        pVirDev->Pop();
        for ( auto const& rPoly : rPolyPolygon )
            pVirDev->DrawPolyLine( rPoly, aLineInfo );
    }
    else
        pVirDev->DrawPolyPolygon( rPolyPolygon );
}

void OS2METReader::AddPointsToArea(const tools::Polygon & rPoly)
{
    sal_uInt16 nOldSize, nNewSize,i;

    if (pAreaStack==nullptr || rPoly.GetSize()==0) return;
    tools::PolyPolygon * pPP=&(pAreaStack->aPPoly);
    if (pPP->Count()==0 || pAreaStack->bClosed) pPP->Insert(rPoly);
    else {
        tools::Polygon aLastPoly(pPP->GetObject(pPP->Count()-1));
        nOldSize=aLastPoly.GetSize();
        if (nOldSize && aLastPoly.GetPoint(nOldSize-1)==rPoly.GetPoint(0)) nOldSize--;
        nNewSize=nOldSize+rPoly.GetSize();
        aLastPoly.SetSize(nNewSize);
        for (i=nOldSize; i<nNewSize; i++) {
            aLastPoly.SetPoint(rPoly.GetPoint(i-nOldSize),i);
        }
        pPP->Replace(aLastPoly,pPP->Count()-1);
    }
    pAreaStack->bClosed=false;
}

void OS2METReader::AddPointsToPath(const tools::Polygon & rPoly)
{
    sal_uInt16 nOldSize, nNewSize,i;

    if (pPathStack==nullptr || rPoly.GetSize()==0) return;
    tools::PolyPolygon * pPP=&(pPathStack->aPPoly);
    if (pPP->Count()==0 /*|| pPathStack->bClosed==sal_True*/) pPP->Insert(rPoly);
    else {
        tools::Polygon aLastPoly(pPP->GetObject(pPP->Count()-1));
        nOldSize=aLastPoly.GetSize();
        if (nOldSize && aLastPoly.GetPoint(nOldSize-1)!=rPoly.GetPoint(0)) pPP->Insert(rPoly);
        else {
            nOldSize--;
            nNewSize=nOldSize+rPoly.GetSize();
            aLastPoly.SetSize(nNewSize);
            for (i=nOldSize; i<nNewSize; i++) {
                aLastPoly.SetPoint(rPoly.GetPoint(i-nOldSize),i);
            }
            pPP->Replace(aLastPoly,pPP->Count()-1);
        }
    }
    pPathStack->bClosed=false;
}

void OS2METReader::CloseFigure()
{
    if (pAreaStack!=nullptr) pAreaStack->bClosed=true;
    else if (pPathStack!=nullptr) pPathStack->bClosed=true;
}

void OS2METReader::PushAttr(sal_uInt16 nPushOrder)
{
    OSAttr * p;
    p=new OSAttr;
    *p=aAttr;
    p->pSucc=pAttrStack; pAttrStack=p;
    p->nPushOrder=nPushOrder;
}

void OS2METReader::PopAttr()
{
    OSAttr * p=pAttrStack;

    if (p==nullptr) return;
    switch (p->nPushOrder) {

        case GOrdPIvAtr:
            switch (p->nIvAttrA) {
                case 1: switch (p->nIvAttrP) {
                    case 1: aAttr.aLinCol=p->aLinCol; break;
                    case 2: aAttr.aChrCol=p->aChrCol; break;
                    case 3: aAttr.aMrkCol=p->aMrkCol; break;
                    case 4: aAttr.aPatCol=p->aPatCol; break;
                    case 5: aAttr.aImgCol=p->aImgCol; break;
                } break;
                case 2: switch (p->nIvAttrP) {
                    case 1: aAttr.aLinBgCol=p->aLinBgCol; break;
                    case 2: aAttr.aChrBgCol=p->aChrBgCol; break;
                    case 3: aAttr.aMrkBgCol=p->aMrkBgCol; break;
                    case 4: aAttr.aPatBgCol=p->aPatBgCol; break;
                    case 5: aAttr.aImgBgCol=p->aImgBgCol; break;
                } break;
                case 3: switch (p->nIvAttrP) {
                    case 1: aAttr.eLinMix=p->eLinMix; break;
                    case 2: aAttr.eChrMix=p->eChrMix; break;
                    case 3: aAttr.eMrkMix=p->eMrkMix; break;
                    case 4: aAttr.ePatMix=p->ePatMix; break;
                    case 5: aAttr.eImgMix=p->eImgMix; break;
                } break;
                case 4: switch (p->nIvAttrP) {
                    case 1: aAttr.eLinBgMix=p->eLinBgMix; break;
                    case 2: aAttr.eChrBgMix=p->eChrBgMix; break;
                    case 3: aAttr.eMrkBgMix=p->eMrkBgMix; break;
                    case 4: aAttr.ePatBgMix=p->ePatBgMix; break;
                    case 5: aAttr.eImgBgMix=p->eImgBgMix; break;
                } break;
            }
            break;

        case GOrdPLnTyp: aAttr.eLinStyle=p->eLinStyle; break;

        case GOrdPLnWdt: aAttr.nLinWidth=p->nLinWidth; break;

        case GOrdPStLWd: aAttr.nStrLinWidth=p->nStrLinWidth; break;

        case GOrdPChSet: aAttr.nChrSet=p->nChrSet; break;

        case GOrdPChAng: aAttr.nChrAng=p->nChrAng; break;

        case GOrdPMixMd:
            aAttr.eLinMix=p->eLinMix;
            aAttr.eChrMix=p->eChrMix;
            aAttr.eMrkMix=p->eMrkMix;
            aAttr.ePatMix=p->ePatMix;
            aAttr.eImgMix=p->eImgMix;
            break;

        case GOrdPBgMix:
            aAttr.eLinBgMix=p->eLinBgMix;
            aAttr.eChrBgMix=p->eChrBgMix;
            aAttr.eMrkBgMix=p->eMrkBgMix;
            aAttr.ePatBgMix=p->ePatBgMix;
            aAttr.eImgBgMix=p->eImgBgMix;
            break;

        case GOrdPPtSym: aAttr.bFill = p->bFill; break;

        case GOrdPColor:
        case GOrdPIxCol:
        case GOrdPXtCol:
            aAttr.aLinCol=p->aLinCol;
            aAttr.aChrCol=p->aChrCol;
            aAttr.aMrkCol=p->aMrkCol;
            aAttr.aPatCol=p->aPatCol;
            aAttr.aImgCol=p->aImgCol;
            break;

        case GOrdPBgCol:
        case GOrdPBxCol:
            aAttr.aLinBgCol=p->aLinBgCol;
            aAttr.aChrBgCol=p->aChrBgCol;
            aAttr.aMrkBgCol=p->aMrkBgCol;
            aAttr.aPatBgCol=p->aPatBgCol;
            aAttr.aImgBgCol=p->aImgBgCol;
            break;

        case GOrdPMkPrc: aAttr.nMrkPrec=aDefAttr.nMrkPrec; break;

        case GOrdPMkSet: aAttr.nMrkSet=aDefAttr.nMrkSet; break;

        case GOrdPMkSym: aAttr.nMrkSymbol=aDefAttr.nMrkSymbol; break;

        case GOrdPMkCel: aAttr.aMrkCellSize=aDefAttr.aMrkCellSize; break;

        case GOrdPArcPa:
            aAttr.nArcP=p->nArcP; aAttr.nArcQ=p->nArcQ;
            aAttr.nArcR=p->nArcR; aAttr.nArcS=p->nArcS;
            break;

        case GOrdPCrPos:
            aAttr.aCurPos=p->aCurPos;
            break;
    }
    pAttrStack=p->pSucc;
    delete p;
}

void OS2METReader::ChangeBrush(const Color& rPatColor, bool bFill )
{
    if( bFill )
        pVirDev->SetFillColor( rPatColor );
    else
        pVirDev->SetFillColor();
}

void OS2METReader::SetPen( const Color& rColor, sal_uInt16 nLineWidth, PenStyle ePenStyle )
{
    LineStyle eLineStyle( LineStyle::Solid );

    if ( pVirDev->GetLineColor() != rColor )
    {
        if (rColor == COL_TRANSPARENT)
            pVirDev->SetLineColor();
        else
            pVirDev->SetLineColor( rColor );
    }
    aLineInfo.SetWidth( nLineWidth );

    if (ePenStyle == PEN_NULL)
        eLineStyle = LineStyle::NONE;
    else if (ePenStyle == PEN_DASHDOT || ePenStyle == PEN_DOT || ePenStyle == PEN_DASH)
    {
        sal_uInt16 nDotCount = 0;
        sal_uInt16 nDashCount = 0;
        if (ePenStyle == PEN_DASHDOT)
        {
            nDashCount++;
            nDotCount++;
        }
        else if (ePenStyle == PEN_DOT)
            nDotCount++;
        else  // (ePenStyle == PEN_DASH)
            nDashCount++;
        aLineInfo.SetDotCount( nDotCount );
        aLineInfo.SetDashCount( nDashCount );
        aLineInfo.SetDistance( nLineWidth );
        aLineInfo.SetDotLen( nLineWidth );
        aLineInfo.SetDashLen( nLineWidth << 2 );
        eLineStyle = LineStyle::Dash;
    }
    aLineInfo.SetStyle( eLineStyle );
}

void OS2METReader::SetRasterOp(RasterOp eROP)
{
    if (pVirDev->GetRasterOp()!=eROP) pVirDev->SetRasterOp(eROP);
}

void OS2METReader::SetPalette0RGB(sal_uInt16 nIndex, sal_uInt32 nCol)
{
    if (pPaletteStack==nullptr) {
        pPaletteStack=new OSPalette;
        pPaletteStack->pSucc=nullptr;
        pPaletteStack->p0RGB=nullptr;
        pPaletteStack->nSize=0;
    }
    if (pPaletteStack->p0RGB==nullptr || nIndex>=pPaletteStack->nSize) {
        sal_uInt32 * pOld0RGB=pPaletteStack->p0RGB;
        size_t nOldSize = pPaletteStack->nSize;
        if (pOld0RGB==nullptr) nOldSize=0;
        pPaletteStack->nSize=2*(nIndex+1);
        if (pPaletteStack->nSize<256) pPaletteStack->nSize=256;
        pPaletteStack->p0RGB = new sal_uInt32[pPaletteStack->nSize];
        for (size_t i=0; i < pPaletteStack->nSize; ++i)
        {
            if (i<nOldSize) pPaletteStack->p0RGB[i]=pOld0RGB[i];
            else if (i==0) pPaletteStack->p0RGB[i]=0x00ffffff;
            else pPaletteStack->p0RGB[i]=0;
        }
        delete[] pOld0RGB;
    }
    pPaletteStack->p0RGB[nIndex]=nCol;
}

sal_uInt32 OS2METReader::GetPalette0RGB(sal_uInt32 nIndex) const
{
    if (pPaletteStack!=nullptr && pPaletteStack->p0RGB!=nullptr &&
        pPaletteStack->nSize>nIndex) nIndex=pPaletteStack->p0RGB[nIndex];
    return nIndex;
}

Color OS2METReader::GetPaletteColor(sal_uInt32 nIndex) const
{
    nIndex=GetPalette0RGB(nIndex);
    return Color(sal::static_int_cast< sal_uInt8 >((nIndex>>16)&0xff),
                 sal::static_int_cast< sal_uInt8 >((nIndex>>8)&0xff),
                 sal::static_int_cast< sal_uInt8 >(nIndex&0xff));
}

sal_uInt16 OS2METReader::ReadBigEndianWord()
{
    sal_uInt8 nLo(0), nHi(0);
    pOS2MET->ReadUChar( nHi ).ReadUChar( nLo );
    return (static_cast<sal_uInt16>(nHi)<<8)|(static_cast<sal_uInt16>(nLo)&0x00ff);
}

sal_uInt32 OS2METReader::ReadBigEndian3BytesLong()
{
    sal_uInt8 nHi(0);
    pOS2MET->ReadUChar( nHi );
    sal_uInt16 nLo = ReadBigEndianWord();
    return ((static_cast<sal_uInt32>(nHi)<<16)&0x00ff0000)|static_cast<sal_uInt32>(nLo);
}

sal_uInt32 OS2METReader::ReadLittleEndian3BytesLong()
{
    sal_uInt8 nHi(0), nMed(0), nLo(0);

    pOS2MET->ReadUChar( nLo ).ReadUChar( nMed ).ReadUChar( nHi );
    return ((static_cast<sal_uInt32>(nHi)&0xff)<<16)|((static_cast<sal_uInt32>(nMed)&0xff)<<8)|(static_cast<sal_uInt32>(nLo)&0xff);
}

sal_Int32 OS2METReader::ReadCoord(bool b32)
{
    sal_Int32 l(0);

    if (b32) pOS2MET->ReadInt32( l );
    else { short s(0); pOS2MET->ReadInt16( s ); l = static_cast<sal_Int32>(s); }
    return l;
}

Point OS2METReader::ReadPoint( const bool bAdjustBoundRect )
{
    sal_Int32 x = ReadCoord(bCoord32);
    sal_Int32 y = ReadCoord(bCoord32);
    x=x-aBoundingRect.Left();
    y=aBoundingRect.Bottom()-y;

    if (bAdjustBoundRect)
    {
        if (x == SAL_MAX_INT32 || y == SAL_MAX_INT32)
            pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
        else
            aCalcBndRect.Union(tools::Rectangle(x, y, x + 1, y + 1));
    }

    return Point(x,y);
}

RasterOp OS2METReader::OS2MixToRasterOp(sal_uInt8 nMix)
{
    switch (nMix) {
        case 0x0c: return RasterOp::Invert;
        case 0x04: return RasterOp::Xor;
        case 0x0b: return RasterOp::Xor;
        default:   return RasterOp::OverPaint;
    }
}

void OS2METReader::ReadLine(bool bGivenPos, sal_uInt16 nOrderLen)
{
    sal_uInt16 i,nPolySize;

    if (bCoord32) nPolySize=nOrderLen/8; else nPolySize=nOrderLen/4;
    if (!bGivenPos) nPolySize++;
    if (nPolySize==0) return;
    tools::Polygon aPolygon(nPolySize);
    for (i=0; i<nPolySize; i++) {
        if (i==0 && !bGivenPos) aPolygon.SetPoint(aAttr.aCurPos,i);
        else aPolygon.SetPoint(ReadPoint(),i);
    }
    aAttr.aCurPos=aPolygon.GetPoint(nPolySize-1);
    if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
    else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
    else
    {
        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        SetRasterOp(aAttr.eLinMix);
        DrawPolyLine( aPolygon );
    }
}

void OS2METReader::ReadRelLine(bool bGivenPos, sal_uInt16 nOrderLen)
{
    sal_uInt16 i,nPolySize;
    Point aP0;

    if (bGivenPos) {
        aP0=ReadPoint();
        if (bCoord32) nOrderLen-=8; else nOrderLen-=4;
    }
    else aP0=aAttr.aCurPos;
    if (nOrderLen > pOS2MET->remainingSize())
        throw css::uno::Exception(u"attempt to read past end of input"_ustr, nullptr);
    nPolySize=nOrderLen/2;
    if (nPolySize==0) return;
    tools::Polygon aPolygon(nPolySize);
    for (i=0; i<nPolySize; i++) {
        sal_Int8 nsignedbyte;
        pOS2MET->ReadSChar( nsignedbyte ); aP0.AdjustX(static_cast<sal_Int32>(nsignedbyte));
        pOS2MET->ReadSChar( nsignedbyte ); aP0.AdjustY(-static_cast<sal_Int32>(nsignedbyte));
        aCalcBndRect.Union(tools::Rectangle(aP0,Size(1,1)));
        aPolygon.SetPoint(aP0,i);
    }
    aAttr.aCurPos=aPolygon.GetPoint(nPolySize-1);
    if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
    else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
    else
    {
        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        SetRasterOp(aAttr.eLinMix);
        DrawPolyLine( aPolygon );
    }
}

void OS2METReader::ReadBox(bool bGivenPos)
{
    sal_uInt8       nFlags;
    Point       P0;

    pOS2MET->ReadUChar( nFlags );
    pOS2MET->SeekRel(1);

    if ( bGivenPos )
        P0 = ReadPoint();
    else
        P0 = aAttr.aCurPos;

    aAttr.aCurPos = ReadPoint();
    sal_Int32 nHRound = ReadCoord(bCoord32);
    sal_Int32 nVRound = ReadCoord(bCoord32);

    if (!pOS2MET->good())
    {
        SAL_WARN("filter.os2met", "OS2METReader::ReadBox: short read");
        return;
    }

    tools::Rectangle aBoxRect( P0, aAttr.aCurPos );

    if ( pAreaStack )
        AddPointsToArea( tools::Polygon( aBoxRect ) );
    else if ( pPathStack )
        AddPointsToPath( tools::Polygon( aBoxRect ) );
    else
    {
        if ( nFlags & 0x20 )
            SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        else
            SetPen( COL_TRANSPARENT );

        if ( nFlags & 0x40 )
        {
            ChangeBrush(aAttr.aPatCol, aAttr.bFill);
            SetRasterOp(aAttr.ePatMix);
        }
        else
        {
            ChangeBrush( COL_TRANSPARENT, false );
            SetRasterOp(aAttr.eLinMix);
        }

        if ( IsLineInfo() )
        {
            tools::Polygon aPolygon( aBoxRect, nHRound, nVRound );
            if ( nFlags & 0x40 )
            {
                auto popIt = pVirDev->ScopedPush(vcl::PushFlags::LINECOLOR);
                pVirDev->SetLineColor( COL_TRANSPARENT );
                pVirDev->DrawRect( aBoxRect, nHRound, nVRound );
            }
            pVirDev->DrawPolyLine( aPolygon, aLineInfo );
        }
        else
            pVirDev->DrawRect( aBoxRect, nHRound, nVRound );
    }
}

void OS2METReader::ReadBitBlt()
{
    pOS2MET->SeekRel(4);
    sal_uInt32 nID(0);
    pOS2MET->ReadUInt32( nID );
    pOS2MET->SeekRel(4);
    Point aP1 = ReadPoint();
    Point aP2 = ReadPoint();
    if (aP1.X() > aP2.X()) { auto nt=aP1.X(); aP1.setX(aP2.X() ); aP2.setX(nt ); }
    if (aP1.Y() > aP2.Y()) { auto nt=aP1.Y(); aP1.setY(aP2.Y() ); aP2.setY(nt ); }
    Size aSize(aP2.X() - aP1.X(), aP2.Y() - aP1.Y());

    OSBitmap* pB = pBitmapList;
    while (pB!=nullptr && pB->nID!=nID) pB=pB->pSucc;
    if (pB!=nullptr) {
        SetRasterOp(aAttr.ePatMix);
        pVirDev->DrawBitmap(aP1,aSize,pB->aBitmap);
    }
}

void OS2METReader::ReadChrStr(bool bGivenPos, bool bMove, bool bExtra, sal_uInt16 nOrderLen)
{
    Point aP0;
    sal_uInt16 nLen;
    OSFont * pF;
    vcl::Font aFont;
    Size aSize;

    pF = pFontList;
    while (pF!=nullptr && pF->nID!=aAttr.nChrSet) pF=pF->pSucc;
    if (pF!=nullptr)
        aFont = pF->aFont;
    aFont.SetColor(aAttr.aChrCol);
    aFont.SetFontSize(Size(0,aAttr.nChrCellHeight));
    if ( aAttr.nChrAng )
        aFont.SetOrientation(aAttr.nChrAng);

    if (bGivenPos)
        aP0 = ReadPoint();
    else
        aP0 = aAttr.aCurPos;
    if (bExtra)
    {
        pOS2MET->SeekRel(2);
        ReadPoint( false );
        ReadPoint( false );
        pOS2MET->ReadUInt16( nLen );
    }
    else
    {
        if ( !bGivenPos )
            nLen = nOrderLen;
        else if ( bCoord32 )
            nLen = nOrderLen-8;
        else
            nLen = nOrderLen-4;
    }
    if (!pOS2MET->good() || nLen > pOS2MET->remainingSize())
        throw css::uno::Exception(u"attempt to read past end of input"_ustr, nullptr);
    std::unique_ptr<char[]> pChr(new char[nLen+1]);
    for (sal_uInt16 i=0; i<nLen; i++)
        pOS2MET->ReadChar( pChr[i] );
    pChr[nLen] = 0;
    OUString aStr( pChr.get(), strlen(pChr.get()), osl_getThreadTextEncoding() );
    SetRasterOp(aAttr.eChrMix);
    if (pVirDev->GetFont()!=aFont)
        pVirDev->SetFont(aFont);
    pVirDev->DrawText(aP0,aStr);

    aSize = Size( pVirDev->GetTextWidth(aStr), pVirDev->GetTextHeight() );
    if ( !aAttr.nChrAng )
    {
        aCalcBndRect.Union(tools::Rectangle( Point(aP0.X(),aP0.Y()-aSize.Height()),
                                      Size(aSize.Width(),aSize.Height()*2)));
        if (bMove)
            aAttr.aCurPos = Point( aP0.X() + aSize.Width(), aP0.Y());
    }
    else
    {
        tools::Polygon aDummyPoly(4);

        aDummyPoly.SetPoint( Point( aP0.X(), aP0.Y() ), 0);                                 // TOP LEFT
        aDummyPoly.SetPoint( Point( aP0.X(), aP0.Y() - aSize.Height() ), 1);                // BOTTOM LEFT
        aDummyPoly.SetPoint( Point( aP0.X() + aSize.Width(), aP0.Y() ), 2);                 // TOP RIGHT
        aDummyPoly.SetPoint( Point( aP0.X() + aSize.Width(), aP0.Y() - aSize.Height() ), 3);// BOTTOM RIGHT
        aDummyPoly.Rotate( aP0, aAttr.nChrAng );
        if ( bMove )
            aAttr.aCurPos = aDummyPoly.GetPoint( 0 );
        aCalcBndRect.Union( tools::Rectangle( aDummyPoly.GetPoint( 0 ), aDummyPoly.GetPoint( 3 ) ) );
        aCalcBndRect.Union( tools::Rectangle( aDummyPoly.GetPoint( 1 ), aDummyPoly.GetPoint( 2 ) ) );
    }
}

void OS2METReader::ReadArc(bool bGivenPos)
{
    Point aP1, aP2, aP3;
    double x1,y1,x2,y2,x3,y3,p,q,cx,cy,ncx,ncy,r,rx,ry,w1,w3;
    if (bGivenPos) aP1=ReadPoint(); else aP1=aAttr.aCurPos;
    aP2=ReadPoint(); aP3=ReadPoint();
    aAttr.aCurPos=aP3;
    SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
    SetRasterOp(aAttr.eLinMix);
    // Ok, given are 3 point of the ellipse, and the relation
    // of width and height (as p to q):
    x1=aP1.X(); y1=aP1.Y();
    x2=aP2.X(); y2=aP2.Y();
    x3=aP3.X(); y3=aP3.Y();
    p=aAttr.nArcP;q=aAttr.nArcQ;
    // Calculation of the center point cx, cy of the ellipse:
    ncy=2*p*p*((y3-y1)*(x2-x1)-(y1-y2)*(x1-x3));
    ncx=2*q*q*(x2-x1);
    if ( (ncx<0.001 && ncx>-0.001) || (ncy<0.001 && ncy>-0.001) ) {
        // Calculation impossible, points are all on the same straight line
        pVirDev->DrawLine(aP1,aP2);
        pVirDev->DrawLine(aP2,aP3);
        return;
    }
    cy=( q*q*((x3*x3-x1*x1)*(x2-x1)+(x2*x2-x1*x1)*(x1-x3)) +
         p*p*((y3*y3-y1*y1)*(x2-x1)+(y2*y2-y1*y1)*(x1-x3)) ) / ncy;
    cx=( q*q*(x2*x2-x1*x1)+p*p*(y2*y2-y1*y1)+cy*2*p*p*(y1-y2) ) / ncx;
    // now we still need the radius in x and y direction:
    r=hypot(q*(x1-cx), p*(y1-cy));
    rx=r/q; ry=r/p;
    // We now have to find out how the starting and the end point
    // have to be chosen so that point no. 2 lies inside the drawn arc:
    w1=fmod((atan2(x1-cx,y1-cy)-atan2(x2-cx,y2-cy)),tau); if (w1<0) w1+=tau;
    w3=fmod((atan2(x3-cx,y3-cy)-atan2(x2-cx,y2-cy)),tau); if (w3<0) w3+=tau;
    if (w3<w1) {
        pVirDev->DrawArc(tools::Rectangle(static_cast<sal_Int32>(cx-rx),static_cast<sal_Int32>(cy-ry),
                                   static_cast<sal_Int32>(cx+rx),static_cast<sal_Int32>(cy+ry)),aP1,aP3);
    }
    else {
        pVirDev->DrawArc(tools::Rectangle(static_cast<sal_Int32>(cx-rx),static_cast<sal_Int32>(cy-ry),
                                   static_cast<sal_Int32>(cx+rx),static_cast<sal_Int32>(cy+ry)),aP3,aP1);
    }
}

void OS2METReader::ReadFullArc(bool bGivenPos, sal_uInt16 nOrderSize)
{
    Point aCenter;
    tools::Rectangle aRect;

    if (bGivenPos) {
        aCenter=ReadPoint();
        if (bCoord32) nOrderSize-=8; else nOrderSize-=4;
    }
    else aCenter=aAttr.aCurPos;

    sal_Int32 nP = aAttr.nArcP;
    sal_Int32 nQ = aAttr.nArcQ;
    if (nP < 0)
        nP = o3tl::saturating_toggle_sign(nP);
    if (nQ < 0)
        nQ = o3tl::saturating_toggle_sign(nQ);
    sal_uInt32 nMul(0);
    if (nOrderSize>=4)
        pOS2MET->ReadUInt32( nMul );
    else {
        sal_uInt16 nMulS(0);
        pOS2MET->ReadUInt16( nMulS );
        nMul=static_cast<sal_uInt32>(nMulS)<<8;
    }
    if (nMul!=0x00010000) {
        nP=(nP*nMul)>>16;
        nQ=(nQ*nMul)>>16;
    }

    aRect=tools::Rectangle(aCenter.X()-nP,aCenter.Y()-nQ,
                    aCenter.X()+nP,aCenter.Y()+nQ);
    aCalcBndRect.Union(aRect);

    if (pAreaStack!=nullptr) {
        ChangeBrush(aAttr.aPatCol, aAttr.bFill);
        SetRasterOp(aAttr.ePatMix);
        if ((pAreaStack->nFlags&0x40)!=0)
            SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        else
            SetPen( COL_TRANSPARENT, 0, PEN_NULL );
    }
    else
    {
        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        ChangeBrush(COL_TRANSPARENT, false);
        SetRasterOp(aAttr.eLinMix);
    }
    pVirDev->DrawEllipse(aRect);
}

void OS2METReader::ReadPartialArc(bool bGivenPos, sal_uInt16 nOrderSize)
{
    Point aP0, aCenter,aPStart,aPEnd;
    tools::Rectangle aRect;

    if (bGivenPos) {
        aP0=ReadPoint();
        if (bCoord32) nOrderSize-=8; else nOrderSize-=4;
    }
    else aP0=aAttr.aCurPos;
    aCenter=ReadPoint();

    sal_Int32 nP = aAttr.nArcP;
    sal_Int32 nQ = aAttr.nArcQ;
    if (nP < 0)
        nP = o3tl::saturating_toggle_sign(nP);
    if (nQ < 0)
        nQ = o3tl::saturating_toggle_sign(nQ);
    sal_uInt32 nMul(0);
    if (nOrderSize>=12)
        pOS2MET->ReadUInt32( nMul );
    else {
        sal_uInt16 nMulS(0);
        pOS2MET->ReadUInt16( nMulS );
        nMul=static_cast<sal_uInt32>(nMulS)<<8;
    }
    if (nMul!=0x00010000) {
        nP=(nP*nMul)>>16;
        nQ=(nQ*nMul)>>16;
    }

    sal_Int32 nStart(0), nSweep(0);
    pOS2MET->ReadInt32( nStart ).ReadInt32( nSweep );
    double fStart = basegfx::deg2rad<65536>(static_cast<double>(nStart));
    double fEnd = fStart+ basegfx::deg2rad<65536>(static_cast<double>(nSweep));
    aPStart=Point(aCenter.X()+static_cast<sal_Int32>( cos(fStart)*nP),
                  aCenter.Y()+static_cast<sal_Int32>(-sin(fStart)*nQ));
    aPEnd=  Point(aCenter.X()+static_cast<sal_Int32>( cos(fEnd)*nP),
                  aCenter.Y()+static_cast<sal_Int32>(-sin(fEnd)*nQ));

    aRect=tools::Rectangle(aCenter.X()-nP,aCenter.Y()-nQ,
                    aCenter.X()+nP,aCenter.Y()+nQ);
    aCalcBndRect.Union(aRect);

    SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
    SetRasterOp(aAttr.eLinMix);

    pVirDev->DrawLine(aP0,aPStart);
    pVirDev->DrawArc(aRect,aPStart,aPEnd);
    aAttr.aCurPos=aPEnd;
}

void OS2METReader::ReadPolygons()
{
    tools::PolyPolygon aPolyPoly;
    tools::Polygon aPoly;
    Point aPoint;

    sal_uInt8 nFlags(0);
    sal_uInt32 nNumPolys(0);
    pOS2MET->ReadUChar(nFlags).ReadUInt32(nNumPolys);

    if (!pOS2MET->good() || nNumPolys > SAL_MAX_UINT16)
    {
        pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
        ErrorCode=11;
        return;
    }

    for (sal_uInt32 i=0; i<nNumPolys; ++i)
    {
        sal_uInt32 nNumPoints(0);
        pOS2MET->ReadUInt32(nNumPoints);
        sal_uInt32 nLimit = SAL_MAX_UINT16;
        if (i==0) --nLimit;
        if (!pOS2MET->good() || nNumPoints > nLimit)
        {
            pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
            ErrorCode=11;
            return;
        }
        if (i==0) ++nNumPoints;
        aPoly.SetSize(static_cast<short>(nNumPoints));
        for (sal_uInt32 j=0; j<nNumPoints; ++j)
        {
            if (i==0 && j==0) aPoint=aAttr.aCurPos;
            else aPoint=ReadPoint();
            aPoly.SetPoint(aPoint,static_cast<short>(j));
            if (i==nNumPolys-1 && j==nNumPoints-1) aAttr.aCurPos=aPoint;
        }
        aPolyPoly.Insert(aPoly);
    }

    ChangeBrush(aAttr.aPatCol, aAttr.bFill);
    SetRasterOp(aAttr.ePatMix);
    if ((nFlags&0x01)!=0)
        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
    else
        SetPen( COL_TRANSPARENT, 0, PEN_NULL );
    DrawPolyPolygon( aPolyPoly );
}

void OS2METReader::ReadBezier(bool bGivenPos, sal_uInt16 nOrderLen)
{
    sal_uInt16 i, nNumPoints = nOrderLen / ( bCoord32 ? 8 : 4 );

    if( !bGivenPos )
        nNumPoints++;

    if( !nNumPoints )
        return;

    tools::Polygon aPolygon( nNumPoints );

    for( i=0; i < nNumPoints; i++ )
    {
        if( i==0 && !bGivenPos)
            aPolygon.SetPoint( aAttr.aCurPos, i );
        else
            aPolygon.SetPoint( ReadPoint(), i );
    }

    if( !( nNumPoints % 4 ) )
    {
        // create bezier polygon
        const sal_uInt16 nSegPoints = 25;
        const sal_uInt16 nSegments = aPolygon.GetSize() >> 2;
        tools::Polygon aBezPoly( nSegments * nSegPoints );

        sal_uInt16 nSeg, nBezPos, nStartPos;
        for( nSeg = 0, nBezPos = 0, nStartPos = 0; nSeg < nSegments; nSeg++, nStartPos += 4 )
        {
            const tools::Polygon aSegPoly( aPolygon[ nStartPos ], aPolygon[ nStartPos + 1 ],
                                           aPolygon[ nStartPos + 3 ], aPolygon[ nStartPos + 2 ],
                                           nSegPoints );

            for( sal_uInt16 nSegPos = 0; nSegPos < nSegPoints; )
                aBezPoly[ nBezPos++ ] = aSegPoly[ nSegPos++ ];
        }

        nNumPoints = nBezPos;

        if( nNumPoints != aBezPoly.GetSize() )
            aBezPoly.SetSize( nNumPoints );

        aPolygon = std::move(aBezPoly);
    }

    aAttr.aCurPos = aPolygon[ nNumPoints - 1 ];

    if (pAreaStack!=nullptr)
        AddPointsToArea(aPolygon);
    else if (pPathStack!=nullptr)
        AddPointsToPath(aPolygon);
    else
    {
        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        SetRasterOp(aAttr.eLinMix);
        DrawPolyLine( aPolygon );
    }
}

void OS2METReader::ReadFillet(bool bGivenPos, sal_uInt16 nOrderLen)
{
    sal_uInt16 i,nNumPoints;

    if (bCoord32) nNumPoints=nOrderLen/8; else nNumPoints=nOrderLen/4;
    if (!bGivenPos) nNumPoints++;
    if (nNumPoints==0) return;
    tools::Polygon aPolygon(nNumPoints);
    for (i=0; i<nNumPoints; i++) {
        if (i==0 && !bGivenPos) aPolygon.SetPoint(aAttr.aCurPos,i);
        else aPolygon.SetPoint(ReadPoint(),i);
    }
    aAttr.aCurPos=aPolygon.GetPoint(nNumPoints-1);
    if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
    else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
    else {
        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        SetRasterOp(aAttr.eLinMix);
        DrawPolyLine( aPolygon );
    }
}

void OS2METReader::ReadFilletSharp(bool bGivenPos, sal_uInt16 nOrderLen)
{
    if (bGivenPos) {
        aAttr.aCurPos=ReadPoint();
        if (bCoord32) nOrderLen-=8; else nOrderLen-=4;
    }

    sal_uInt16 nNumPoints;
    if (bCoord32) nNumPoints=1+nOrderLen/10;
    else nNumPoints=1+nOrderLen/6;

    tools::Polygon aPolygon(nNumPoints);
    aPolygon.SetPoint(aAttr.aCurPos, 0);
    for (sal_uInt16 i = 1; i <nNumPoints; ++i)
        aPolygon.SetPoint(ReadPoint(), i);

    if (!pOS2MET->good())
    {
        SAL_WARN("filter.os2met", "OS2METReader::ReadFilletSharp: short read");
        return;
    }

    aAttr.aCurPos=aPolygon.GetPoint(nNumPoints-1);
    if (pAreaStack!=nullptr) AddPointsToArea(aPolygon);
    else if (pPathStack!=nullptr) AddPointsToPath(aPolygon);
    else
    {
        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
        SetRasterOp(aAttr.eLinMix);
        DrawPolyLine( aPolygon );
    }
}

void OS2METReader::ReadMarker(bool bGivenPos, sal_uInt16 nOrderLen)
{
    sal_uInt16 i,nNumPoints;

    SetPen( aAttr.aMrkCol );
    SetRasterOp(aAttr.eMrkMix);
    if (aAttr.nMrkSymbol>=5 && aAttr.nMrkSymbol<=9)
    {
        ChangeBrush(aAttr.aMrkCol, true);
    }
    else
    {
        ChangeBrush(COL_TRANSPARENT, false);
    }
    if (bCoord32) nNumPoints=nOrderLen/8; else nNumPoints=nOrderLen/4;
    if (!bGivenPos) nNumPoints++;
    for (i=0; i<nNumPoints; i++) {
        if (i!=0 || bGivenPos) aAttr.aCurPos=ReadPoint();
        const auto x = aAttr.aCurPos.X();
        const auto y = aAttr.aCurPos.Y();
        aCalcBndRect.Union(tools::Rectangle(x-5,y-5,x+5,y+5));
        switch (aAttr.nMrkSymbol) {
            case  2:   // PLUS
                pVirDev->DrawLine(Point(x-4,y),Point(x+4,y));
                pVirDev->DrawLine(Point(x,y-4),Point(x,y+4));
                break;
            case  3:   // DIAMOND
            case  7: { // SOLIDDIAMOND
                tools::Polygon aPoly(4);
                aPoly.SetPoint(Point(x,y+4),0);
                aPoly.SetPoint(Point(x+4,y),1);
                aPoly.SetPoint(Point(x,y-4),2);
                aPoly.SetPoint(Point(x-4,y),3);
                pVirDev->DrawPolygon(aPoly);
                break;
            }
            case  4:   // SQUARE
            case  8: { // SOLIDSUARE
                tools::Polygon aPoly(4);
                aPoly.SetPoint(Point(x+4,y+4),0);
                aPoly.SetPoint(Point(x+4,y-4),1);
                aPoly.SetPoint(Point(x-4,y-4),2);
                aPoly.SetPoint(Point(x-4,y+4),3);
                pVirDev->DrawPolygon(aPoly);
                break;
            }
            case  5: { // SIXPOINTSTAR
                tools::Polygon aPoly(12);
                aPoly.SetPoint(Point(x  ,y-4),0);
                aPoly.SetPoint(Point(x+2,y-2),1);
                aPoly.SetPoint(Point(x+4,y-2),2);
                aPoly.SetPoint(Point(x+2,y  ),3);
                aPoly.SetPoint(Point(x+4,y+2),4);
                aPoly.SetPoint(Point(x+2,y+2),5);
                aPoly.SetPoint(Point(x  ,y+4),6);
                aPoly.SetPoint(Point(x-2,y+2),7);
                aPoly.SetPoint(Point(x-4,y+2),8);
                aPoly.SetPoint(Point(x-2,y  ),9);
                aPoly.SetPoint(Point(x-4,y-2),10);
                aPoly.SetPoint(Point(x-2,y-2),11);
                pVirDev->DrawPolygon(aPoly);
                break;
            }
            case  6: { // EIGHTPOINTSTAR
                tools::Polygon aPoly(16);
                aPoly.SetPoint(Point(x  ,y-4),0);
                aPoly.SetPoint(Point(x+1,y-2),1);
                aPoly.SetPoint(Point(x+3,y-3),2);
                aPoly.SetPoint(Point(x+2,y-1),3);
                aPoly.SetPoint(Point(x+4,y  ),4);
                aPoly.SetPoint(Point(x+2,y+1),5);
                aPoly.SetPoint(Point(x+3,y+3),6);
                aPoly.SetPoint(Point(x+1,y+2),7);
                aPoly.SetPoint(Point(x  ,y+4),8);
                aPoly.SetPoint(Point(x-1,y+2),9);
                aPoly.SetPoint(Point(x-3,y+3),10);
                aPoly.SetPoint(Point(x-2,y+1),11);
                aPoly.SetPoint(Point(x-4,y  ),12);
                aPoly.SetPoint(Point(x-2,y-1),13);
                aPoly.SetPoint(Point(x-3,y-3),14);
                aPoly.SetPoint(Point(x-1,y-2),15);
                pVirDev->DrawPolygon(aPoly);
                break;
            }
            case  9:   // DOT
                pVirDev->DrawEllipse(tools::Rectangle(x-1,y-1,x+1,y+1));
                break;
            case 10:   // SMALLCIRCLE
                pVirDev->DrawEllipse(tools::Rectangle(x-2,y-2,x+2,y+2));
                break;
            case 64:   // BLANK
                break;
            default:   // (=1) CROSS
                pVirDev->DrawLine(Point(x-4,y-4),Point(x+4,y+4));
                pVirDev->DrawLine(Point(x-4,y+4),Point(x+4,y-4));
                break;
        }
    }
}

void OS2METReader::ReadOrder(sal_uInt16 nOrderID, sal_uInt16 nOrderLen)
{
    switch (nOrderID) {

        case GOrdGivArc: ReadArc(true); break;
        case GOrdCurArc: ReadArc(false); break;

        case GOrdGivBzr: ReadBezier(true,nOrderLen); break;
        case GOrdCurBzr: ReadBezier(false,nOrderLen); break;

        case GOrdGivBox: ReadBox(true); break;
        case GOrdCurBox: ReadBox(false); break;

        case GOrdGivFil: ReadFillet(true,nOrderLen); break;
        case GOrdCurFil: ReadFillet(false,nOrderLen); break;

        case GOrdGivCrc: ReadFullArc(true,nOrderLen); break;
        case GOrdCurCrc: ReadFullArc(false,nOrderLen); break;

        case GOrdGivLin: ReadLine(true, nOrderLen); break;
        case GOrdCurLin: ReadLine(false, nOrderLen); break;

        case GOrdGivMrk: ReadMarker(true, nOrderLen); break;
        case GOrdCurMrk: ReadMarker(false, nOrderLen); break;

        case GOrdGivArP: ReadPartialArc(true,nOrderLen); break;
        case GOrdCurArP: ReadPartialArc(false,nOrderLen); break;

        case GOrdGivRLn: ReadRelLine(true,nOrderLen); break;
        case GOrdCurRLn: ReadRelLine(false,nOrderLen); break;

        case GOrdGivSFl: ReadFilletSharp(true,nOrderLen); break;
        case GOrdCurSFl: ReadFilletSharp(false,nOrderLen); break;

        case GOrdGivStM: ReadChrStr(true , true , false, nOrderLen); break;
        case GOrdCurStM: ReadChrStr(false, true , false, nOrderLen); break;
        case GOrdGivStr: ReadChrStr(true , false, false, nOrderLen); break;
        case GOrdCurStr: ReadChrStr(false, false, false, nOrderLen); break;
        case GOrdGivStx: ReadChrStr(true , false, true , nOrderLen); break;
        case GOrdCurStx: ReadChrStr(false, false, true , nOrderLen); break;

        case GOrdGivImg: SAL_INFO("filter.os2met","GOrdGivImg");
            break;
        case GOrdCurImg: SAL_INFO("filter.os2met","GOrdCurImg");
            break;
        case GOrdImgDat: SAL_INFO("filter.os2met","GOrdImgDat");
            break;
        case GOrdEndImg: SAL_INFO("filter.os2met","GOrdEndImg");
            break;

        case GOrdBegAra: {
            OSArea * p=new OSArea;
            p->bClosed=false;
            p->pSucc=pAreaStack; pAreaStack=p;
            pOS2MET->ReadUChar( p->nFlags );
            p->aCol=aAttr.aPatCol;
            p->aBgCol=aAttr.aPatBgCol;
            p->eMix=aAttr.ePatMix;
            p->eBgMix=aAttr.ePatBgMix;
            p->bFill=aAttr.bFill;
            break;
        }
        case GOrdEndAra:
        {
            OSArea * p=pAreaStack;
            if ( p )
            {
                pAreaStack = p->pSucc;
                if ( pPathStack )
                {
                    for ( sal_uInt16 i=0; i<p->aPPoly.Count(); i++ )
                    {
                        AddPointsToPath( p->aPPoly.GetObject( i ) );
                        CloseFigure();
                    }
                }
                else
                {
                    if ( ( p->nFlags & 0x40 ) == 0 )
                        SetPen( COL_TRANSPARENT, 0, PEN_NULL );
                    else
                        SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );

                    ChangeBrush(p->aCol, p->bFill);
                    SetRasterOp(p->eMix);
                    DrawPolyPolygon( p->aPPoly );
                }
                delete p;
            }
        }
        break;

        case GOrdBegElm: SAL_INFO("filter.os2met","GOrdBegElm");
            break;
        case GOrdEndElm: SAL_INFO("filter.os2met","GOrdEndElm");
            break;

        case GOrdBegPth: {
            OSPath * p=new OSPath;
            p->pSucc=pPathStack; pPathStack=p;
            pOS2MET->SeekRel(2);
            pOS2MET->ReadUInt32( p->nID );
            p->bClosed=false;
            p->bStroke=false;
            break;
        }
        case GOrdEndPth: {
            OSPath * p, * pprev, * psucc;
            if (pPathStack==nullptr) break;
            p=pPathList; pprev=nullptr;
            while (p!=nullptr) {
                psucc=p->pSucc;
                if (p->nID==pPathStack->nID) {
                    if (pprev==nullptr) pPathList=psucc; else pprev->pSucc=psucc;
                    delete p;
                }
                else pprev=p;
                p=psucc;
            }
            p=pPathStack;
            pPathStack=p->pSucc;
            p->pSucc=pPathList; pPathList=p;
            break;
        }
        case GOrdFilPth:
        {
            sal_uInt32 nID(0);
            sal_uInt16 nDummy(0);
            OSPath* p = pPathList;

            pOS2MET->ReadUInt16( nDummy )
                    .ReadUInt32( nID );

            if ( ! ( nDummy & 0x20 ) )  // #30933# i do not know the exact meaning of this bit,
            {                           // but if set it seems to be better not to fill this path
                while( p && p->nID != nID )
                    p = p->pSucc;

                if( p )
                {
                    if( p->bStroke )
                    {
                        SetPen( aAttr.aPatCol, aAttr.nStrLinWidth );
                        ChangeBrush(COL_TRANSPARENT, false);
                        SetRasterOp( aAttr.ePatMix );
                        if ( IsLineInfo() )
                        {
                            for ( sal_uInt16 i = 0; i < p->aPPoly.Count(); i++ )
                                pVirDev->DrawPolyLine( p->aPPoly.GetObject( i ), aLineInfo );
                        }
                        else
                            pVirDev->DrawPolyPolygon( p->aPPoly );
                    }
                    else
                    {
                        SetPen( COL_TRANSPARENT, 0, PEN_NULL );
                        ChangeBrush( aAttr.aPatCol, aAttr.bFill );
                        SetRasterOp( aAttr.ePatMix );
                        pVirDev->DrawPolyPolygon( p->aPPoly );
                    }
                }
            }
        }
        break;

        case GOrdModPth:
        {
            OSPath* p = pPathList;

            while( p && p->nID != 1 )
                p = p->pSucc;

            if( p )
                p->bStroke = true;
        }
        break;

        case GOrdOutPth:
        {
            sal_uInt32 nID;
            sal_uInt16  i,nC;
            OSPath* p=pPathList;
            pOS2MET->SeekRel(2);
            pOS2MET->ReadUInt32( nID );
            while (p && pOS2MET->good() && p->nID != nID)
                p = p->pSucc;

            if (p)
            {
                SetPen( aAttr.aLinCol, aAttr.nStrLinWidth, aAttr.eLinStyle );
                SetRasterOp(aAttr.eLinMix);
                ChangeBrush(COL_TRANSPARENT, false);
                nC=p->aPPoly.Count();
                for (i=0; i<nC; i++)
                {
                    if (i+1<nC || p->bClosed)
                        DrawPolygon( p->aPPoly.GetObject( i ) );
                    else
                        DrawPolyLine( p->aPPoly.GetObject( i ) );
                }
            }
            break;
        }
        case GOrdSClPth: {
            SAL_INFO("filter.os2met","GOrdSClPth");
            sal_uInt32 nID(0);
            OSPath * p=pPathList;
            pOS2MET->SeekRel(2);
            pOS2MET->ReadUInt32( nID );
            if (nID==0) p=nullptr;
            while (p!=nullptr && p->nID!=nID) p=p->pSucc;
            if (p!=nullptr) pVirDev->SetClipRegion(vcl::Region(p->aPPoly));
            else pVirDev->SetClipRegion();
            break;
        }
        case GOrdNopNop:
            break;
        case GOrdRemark: SAL_INFO("filter.os2met","GOrdRemark");
            break;
        case GOrdSegLab: SAL_INFO("filter.os2met","GOrdSegLab");
            break;

        case GOrdBitBlt: ReadBitBlt(); break;

        case GOrdCalSeg: SAL_INFO("filter.os2met","GOrdCalSeg");
            break;
        case GOrdSSgBnd: SAL_INFO("filter.os2met","GOrdSSgBnd");
            break;
        case GOrdSegChr: SAL_INFO("filter.os2met","GOrdSegChr");
            break;
        case GOrdCloFig:
            CloseFigure();
            break;
        case GOrdEndSym: SAL_INFO("filter.os2met","GOrdEndSym");
            break;
        case GOrdEndPlg: SAL_INFO("filter.os2met","GOrdEndPlg");
            break;
        case GOrdEscape: SAL_INFO("filter.os2met","GOrdEscape");
            break;
        case GOrdExtEsc: SAL_INFO("filter.os2met","GOrdExtEsc");
            break;

        case GOrdPolygn: ReadPolygons(); break;

        case GOrdStkPop: PopAttr(); break;

        case GOrdPIvAtr: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSIvAtr: {
            sal_uInt8 nA(0), nP(0), nFlags(0);
            Color aCol;
            RasterOp eROP;
            pOS2MET->ReadUChar( nA ).ReadUChar( nP ).ReadUChar( nFlags );
            if (nOrderID==GOrdPIvAtr) {
                pAttrStack->nIvAttrA=nA;
                pAttrStack->nIvAttrP=nP;
            }
            if (nA<=2) {
                if ((nFlags&0x80)!=0) {
                    if (nA==1) switch (nP) {
                        case 1: aAttr.aLinCol=aDefAttr.aLinCol; break;
                        case 2: aAttr.aChrCol=aDefAttr.aChrCol; break;
                        case 3: aAttr.aMrkCol=aDefAttr.aMrkCol; break;
                        case 4: aAttr.aPatCol=aDefAttr.aPatCol; break;
                        case 5: aAttr.aImgCol=aDefAttr.aImgCol; break;
                    }
                    else switch (nP) {
                        case 1: aAttr.aLinBgCol=aDefAttr.aLinBgCol; break;
                        case 2: aAttr.aChrBgCol=aDefAttr.aChrBgCol; break;
                        case 3: aAttr.aMrkBgCol=aDefAttr.aMrkBgCol; break;
                        case 4: aAttr.aPatBgCol=aDefAttr.aPatBgCol; break;
                        case 5: aAttr.aImgBgCol=aDefAttr.aImgBgCol; break;
                    }
                }
                else {
                    const auto nVal = ReadLittleEndian3BytesLong();
                    if      ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK;
                    else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE;
                    else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE;
                    else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK;
                    else aCol=GetPaletteColor(nVal);
                    if (nA==1) switch (nP) {
                        case 1: aAttr.aLinCol=aCol; break;
                        case 2: aAttr.aChrCol=aCol; break;
                        case 3: aAttr.aMrkCol=aCol; break;
                        case 4: aAttr.aPatCol=aCol; break;
                        case 5: aAttr.aImgCol=aCol; break;
                    }
                    else switch (nP) {
                        case 1: aAttr.aLinBgCol=aCol; break;
                        case 2: aAttr.aChrBgCol=aCol; break;
                        case 3: aAttr.aMrkBgCol=aCol; break;
                        case 4: aAttr.aPatBgCol=aCol; break;
                        case 5: aAttr.aImgBgCol=aCol; break;
                    }
                }
            }
            else {
                sal_uInt8 nMix(0);
                pOS2MET->ReadUChar( nMix );
                if (nMix==0) {
                    switch (nP) {
                        case 1: aAttr.eLinBgMix=aDefAttr.eLinBgMix; break;
                        case 2: aAttr.eChrBgMix=aDefAttr.eChrBgMix; break;
                        case 3: aAttr.eMrkBgMix=aDefAttr.eMrkBgMix; break;
                        case 4: aAttr.ePatBgMix=aDefAttr.ePatBgMix; break;
                        case 5: aAttr.eImgBgMix=aDefAttr.eImgBgMix; break;
                    }
                }
                else {
                    eROP=OS2MixToRasterOp(nMix);
                    switch (nP) {
                        case 1: aAttr.eLinBgMix=eROP; break;
                        case 2: aAttr.eChrBgMix=eROP; break;
                        case 3: aAttr.eMrkBgMix=eROP; break;
                        case 4: aAttr.ePatBgMix=eROP; break;
                        case 5: aAttr.eImgBgMix=eROP; break;
                    }
                }
            }
            break;
        }
        case GOrdPIxCol: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSIxCol: {
            sal_uInt8 nFlags(0);
            pOS2MET->ReadUChar( nFlags );
            if ((nFlags&0x80)!=0) {
                aAttr.aLinCol=aDefAttr.aLinCol;
                aAttr.aChrCol=aDefAttr.aChrCol;
                aAttr.aMrkCol=aDefAttr.aMrkCol;
                aAttr.aPatCol=aDefAttr.aPatCol;
                aAttr.aImgCol=aDefAttr.aImgCol;
            }
            else {
                Color aCol;
                const auto nVal = ReadLittleEndian3BytesLong();
                if      ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK;
                else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE;
                else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE;
                else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK;
                else aCol=GetPaletteColor(nVal);
                aAttr.aLinCol = aAttr.aChrCol = aAttr.aMrkCol = aAttr.aPatCol =
                aAttr.aImgCol = aCol;
            }
            break;
        }

        case GOrdPColor:
        case GOrdPXtCol: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSColor:
        case GOrdSXtCol: {
            sal_uInt16 nVal(0);
            if (nOrderID==GOrdPColor || nOrderID==GOrdSColor) {
                sal_uInt8 nbyte(0);
                pOS2MET->ReadUChar( nbyte ); nVal=static_cast<sal_uInt16>(nbyte)|0xff00;
            }
            else pOS2MET->ReadUInt16( nVal );
            if (nVal==0x0000 || nVal==0xff00)  {
                aAttr.aLinCol=aDefAttr.aLinCol;
                aAttr.aChrCol=aDefAttr.aChrCol;
                aAttr.aMrkCol=aDefAttr.aMrkCol;
                aAttr.aPatCol=aDefAttr.aPatCol;
                aAttr.aImgCol=aDefAttr.aImgCol;
            }
            else {
                Color aCol;
                if      (nVal==0x0007) aCol=COL_WHITE;
                else if (nVal==0x0008) aCol=COL_BLACK;
                else if (nVal==0xff08) aCol=GetPaletteColor(1);
                else aCol=GetPaletteColor(static_cast<sal_uInt32>(nVal) & 0x000000ff);
                aAttr.aLinCol = aAttr.aChrCol = aAttr.aMrkCol = aAttr.aPatCol =
                aAttr.aImgCol = aCol;
            }
            break;
        }

        case GOrdPBgCol: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSBgCol: {
            sal_uInt16 nVal(0);
            pOS2MET->ReadUInt16( nVal );
            if (nVal==0x0000 || nVal==0xff00)  {
                aAttr.aLinBgCol=aDefAttr.aLinBgCol;
                aAttr.aChrBgCol=aDefAttr.aChrBgCol;
                aAttr.aMrkBgCol=aDefAttr.aMrkBgCol;
                aAttr.aPatBgCol=aDefAttr.aPatBgCol;
                aAttr.aImgBgCol=aDefAttr.aImgBgCol;
            }
            else {
                Color aCol;
                if      (nVal==0x0007) aCol=COL_WHITE;
                else if (nVal==0x0008) aCol=COL_BLACK;
                else if (nVal==0xff08) aCol=GetPaletteColor(0);
                else aCol=GetPaletteColor(static_cast<sal_uInt32>(nVal) & 0x000000ff);
                aAttr.aLinBgCol = aAttr.aChrBgCol = aAttr.aMrkBgCol =
                aAttr.aPatBgCol = aAttr.aImgBgCol = aCol;
            }
            break;
        }
        case GOrdPBxCol: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSBxCol: {
            sal_uInt8 nFlags(0);
            pOS2MET->ReadUChar( nFlags );
            if ((nFlags&0x80)!=0) {
                aAttr.aLinBgCol=aDefAttr.aLinBgCol;
                aAttr.aChrBgCol=aDefAttr.aChrBgCol;
                aAttr.aMrkBgCol=aDefAttr.aMrkBgCol;
                aAttr.aPatBgCol=aDefAttr.aPatBgCol;
                aAttr.aImgBgCol=aDefAttr.aImgBgCol;
            }
            else {
                Color aCol;
                const auto nVal = ReadLittleEndian3BytesLong();
                if      ((nFlags&0x40)!=0 && nVal==1) aCol=COL_BLACK;
                else if ((nFlags&0x40)!=0 && nVal==2) aCol=COL_WHITE;
                else if ((nFlags&0x40)!=0 && nVal==4) aCol=COL_WHITE;
                else if ((nFlags&0x40)!=0 && nVal==5) aCol=COL_BLACK;
                else aCol=GetPaletteColor(nVal);
                aAttr.aLinBgCol = aAttr.aChrBgCol = aAttr.aMrkBgCol =
                aAttr.aPatBgCol = aAttr.aImgBgCol = aCol;
            }
            break;
        }

        case GOrdPMixMd: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSMixMd: {
            sal_uInt8 nMix(0);
            pOS2MET->ReadUChar( nMix );
            if (nMix==0) {
                aAttr.eLinMix=aDefAttr.eLinMix;
                aAttr.eChrMix=aDefAttr.eChrMix;
                aAttr.eMrkMix=aDefAttr.eMrkMix;
                aAttr.ePatMix=aDefAttr.ePatMix;
                aAttr.eImgMix=aDefAttr.eImgMix;
            }
            else {
                aAttr.eLinMix = aAttr.eChrMix = aAttr.eMrkMix =
                aAttr.ePatMix = aAttr.eImgMix = OS2MixToRasterOp(nMix);
            }
            break;
        }
        case GOrdPBgMix: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSBgMix: {
            sal_uInt8 nMix(0);
            pOS2MET->ReadUChar( nMix );
            if (nMix==0) {
                aAttr.eLinBgMix=aDefAttr.eLinBgMix;
                aAttr.eChrBgMix=aDefAttr.eChrBgMix;
                aAttr.eMrkBgMix=aDefAttr.eMrkBgMix;
                aAttr.ePatBgMix=aDefAttr.ePatBgMix;
                aAttr.eImgBgMix=aDefAttr.eImgBgMix;
            }
            else {
                aAttr.eLinBgMix = aAttr.eChrBgMix = aAttr.eMrkBgMix =
                aAttr.ePatBgMix = aAttr.eImgBgMix = OS2MixToRasterOp(nMix);
            }
            break;
        }
        case GOrdPPtSet: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSPtSet: SAL_INFO("filter.os2met","GOrdSPtSet");
            break;

        case GOrdPPtSym: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSPtSym: {
            sal_uInt8 nPatt(0);
            pOS2MET->ReadUChar( nPatt );
            aAttr.bFill = ( nPatt != 0x0f );
            break;
        }

        case GOrdPPtRef: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSPtRef: SAL_INFO("filter.os2met","GOrdSPtRef");
            break;

        case GOrdPLnEnd: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSLnEnd:
            break;

        case GOrdPLnJoi: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSLnJoi:
            break;

        case GOrdPLnTyp: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSLnTyp: {
            sal_uInt8 nType(0);
            pOS2MET->ReadUChar( nType );
            switch (nType) {
                case 0:         aAttr.eLinStyle=aDefAttr.eLinStyle; break;
                case 1: case 4: aAttr.eLinStyle=PEN_DOT; break;
                case 2: case 5: aAttr.eLinStyle=PEN_DASH; break;
                case 3: case 6: aAttr.eLinStyle=PEN_DASHDOT; break;
                case 8:         aAttr.eLinStyle=PEN_NULL; break;
                default:        aAttr.eLinStyle=PEN_SOLID;
            }
            break;
        }
        case GOrdPLnWdt: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSLnWdt: {
            sal_uInt8 nbyte(0);
            pOS2MET->ReadUChar(nbyte);
            if (nbyte > 0)
                aAttr.nLinWidth = static_cast<sal_uInt16>(nbyte)-1;
            else
                aAttr.nLinWidth = aDefAttr.nLinWidth;
            break;
        }
        case GOrdPFrLWd: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSFrLWd:
            break;

        case GOrdPStLWd: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSStLWd :
        {
            sal_uInt8 nFlags(0);

            pOS2MET->ReadUChar( nFlags );
            if ( nFlags & 0x80 )
                aAttr.nStrLinWidth = aDefAttr.nStrLinWidth;
            else
            {
                pOS2MET->SeekRel( 1 );
                sal_Int32 nWd = ReadCoord( bCoord32 );
                if (nWd < 0)
                    nWd = o3tl::saturating_toggle_sign(nWd);
                aAttr.nStrLinWidth = static_cast<sal_uInt16>(nWd);
            }
            break;
        }
        case GOrdPChDir: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChDir:
            break;

        case GOrdPChPrc: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChPrc:
            break;

        case GOrdPChSet: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChSet: {
            sal_uInt8 nbyte(0);
            pOS2MET->ReadUChar( nbyte );
            aAttr.nChrSet=static_cast<sal_uInt32>(nbyte)&0xff;
            break;
        }
        case GOrdPChAng: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChAng: {
            sal_Int32 nX = ReadCoord(bCoord32);
            sal_Int32 nY = ReadCoord(bCoord32);
            if (nX>=0 && nY==0) aAttr.nChrAng=0_deg10;
            else {
                aAttr.nChrAng = Degree10(static_cast<short>(basegfx::rad2deg<10>(atan2(static_cast<double>(nY),static_cast<double>(nX)))));
                while (aAttr.nChrAng < 0_deg10) aAttr.nChrAng += 3600_deg10;
                aAttr.nChrAng %= 3600_deg10;
            }
            break;
        }
        case GOrdPChBrx: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChBrx:
            break;

        case GOrdPChCel: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChCel: {
            sal_uInt16 nLen=nOrderLen;
            (void) ReadCoord(bCoord32); // Width, unused
            auto nHeight = ReadCoord(bCoord32);
            if (nHeight < 0 || nHeight > SAL_MAX_INT16)
            {
                SAL_WARN("filter.os2met", "ignoring out of sane range font height: " << nHeight);
                aAttr.nChrCellHeight = aDefAttr.nChrCellHeight;
            }
            else
                aAttr.nChrCellHeight = nHeight;
            if (bCoord32) nLen-=8; else nLen-=4;
            if (nLen>=4) {
                pOS2MET->SeekRel(4); nLen-=4;
            }
            if (nLen>=2) {
                sal_uInt8 nbyte(0);
                pOS2MET->ReadUChar( nbyte );
                if ((nbyte&0x80)==0 && aAttr.nChrCellHeight == 0)
                    aAttr.nChrCellHeight = aDefAttr.nChrCellHeight;
            }
            break;
        }
        case GOrdPChXtr: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChXtr:
            break;

        case GOrdPChShr: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSChShr:
            break;

        case GOrdPTxAlg: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSTxAlg: SAL_INFO("filter.os2met","GOrdSTxAlg");
            break;

        case GOrdPMkPrc: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSMkPrc: {
            sal_uInt8 nbyte(0);
            pOS2MET->ReadUChar( nbyte );
            if (nbyte==0) aAttr.nMrkPrec=aDefAttr.nMrkPrec;
            else aAttr.nMrkPrec=nbyte;
            break;
        }

        case GOrdPMkSet: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSMkSet: {
            sal_uInt8 nbyte(0);
            pOS2MET->ReadUChar( nbyte );
            if (nbyte==0) aAttr.nMrkSet=aDefAttr.nMrkSet;
            else aAttr.nMrkSet=nbyte;
            break;
        }

        case GOrdPMkSym: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSMkSym: {
            sal_uInt8 nbyte(0);
            pOS2MET->ReadUChar( nbyte );
            if (nbyte==0) aAttr.nMrkSymbol=aDefAttr.nMrkSymbol;
            else aAttr.nMrkSymbol=nbyte;
            break;
        }

        case GOrdPMkCel: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSMkCel: {
            sal_uInt16 nLen=nOrderLen;
            aAttr.aMrkCellSize.setWidth(ReadCoord(bCoord32) );
            aAttr.aMrkCellSize.setHeight(ReadCoord(bCoord32) );
            if (bCoord32) nLen-=8; else nLen-=4;
            if (nLen>=2) {
                sal_uInt8 nbyte(0);
                pOS2MET->ReadUChar( nbyte );
                if ((nbyte&0x80)==0 && aAttr.aMrkCellSize==Size(0,0))
                    aAttr.aMrkCellSize=aDefAttr.aMrkCellSize;
            }
            break;
        }

        case GOrdPArcPa: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSArcPa:
            aAttr.nArcP=ReadCoord(bCoord32);
            aAttr.nArcQ=ReadCoord(bCoord32);
            aAttr.nArcR=ReadCoord(bCoord32);
            aAttr.nArcS=ReadCoord(bCoord32);
            break;

        case GOrdPCrPos: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSCrPos:
            aAttr.aCurPos=ReadPoint();
            break;

        case GOrdPMdTrn: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSMdTrn: SAL_INFO("filter.os2met","GOrdSMdTrn");
            break;

        case GOrdPPkIdn: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSPkIdn: SAL_INFO("filter.os2met","GOrdSPkIdn");
            break;

        case GOrdSVwTrn: SAL_INFO("filter.os2met","GOrdSVwTrn");
            break;

        case GOrdPVwWin: PushAttr(nOrderID);
            [[fallthrough]];
        case GOrdSVwWin: SAL_INFO("filter.os2met","GOrdSVwWin");
            break;
        default: SAL_INFO("filter.os2met","Unknown order: " << nOrderID);
    }
}

void OS2METReader::ReadDsc(sal_uInt16 nDscID)
{
    switch (nDscID) {
        case 0x00f7: { // 'Specify GVM Subset'
            sal_uInt8 nbyte(0);
            pOS2MET->SeekRel(6);
            pOS2MET->ReadUChar( nbyte );
            if      (nbyte==0x05) bCoord32=true;
            else if (nbyte==0x04) bCoord32=false;
            else {
                pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                ErrorCode=1;
            }
            break;
        }
        case 0x00f6:
        {
            // 'Set Picture Descriptor'
            bool b32;
            sal_uInt8 nbyte(0), nUnitType(0);

            pOS2MET->SeekRel(2);
            pOS2MET->ReadUChar( nbyte );

            if (nbyte==0x05)
                b32=true;
            else if(nbyte==0x04)
                b32=false;
            else
            {
                b32 = false;   // -Wall added the case.
                pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                ErrorCode=2;
            }

            pOS2MET->ReadUChar( nUnitType );

            sal_Int32 xr = ReadCoord(b32);
            sal_Int32 yr = ReadCoord(b32);

            ReadCoord(b32);

            if (nUnitType==0x00 && xr>0 && yr>0)
                aGlobMapMode=MapMode(MapUnit::MapInch,Point(0,0),Fraction(10,xr),Fraction(10,yr));
            else if (nUnitType==0x01 && xr>0 && yr>0)
                aGlobMapMode=MapMode(MapUnit::MapCM,Point(0,0),Fraction(10,xr),Fraction(10,yr));
            else
                aGlobMapMode=MapMode();

            sal_Int32 x1 = ReadCoord(b32);
            sal_Int32 x2 = ReadCoord(b32);
            sal_Int32 y1 = ReadCoord(b32);
            sal_Int32 y2 = ReadCoord(b32);

            if (x1>x2)
            {
                const auto nt = x1;
                x1=x2;
                x2=nt;
            }

            if (y1>y2)
            {
                const auto nt = y1;
                y1=y2;
                y2=nt;
            }

            aBoundingRect.SetLeft( x1 );
            aBoundingRect.SetRight( x2 );
            aBoundingRect.SetTop( y1 );
            aBoundingRect.SetBottom( y2 );

            // no output beside this bounding rect
            pVirDev->IntersectClipRegion( tools::Rectangle( Point(), aBoundingRect.GetSize() ) );

            break;
        }
        case 0x0021: // 'Set Current Defaults'
            break;
    }
}

void OS2METReader::ReadImageData(sal_uInt16 nDataID, sal_uInt16 nDataLen)
{
    OSBitmap * p=pBitmapList; if (p==nullptr) return;

    switch (nDataID) {

        case 0x0070:   // Begin Segment
            break;

        case 0x0091:   // Begin Image Content
            break;

        case 0x0094:   // Image Size
            pOS2MET->SeekRel(5);
            p->nHeight=ReadBigEndianWord();
            p->nWidth=ReadBigEndianWord();
            break;

        case 0x0095:   // Image Encoding
            break;

        case 0x0096: { // Image IDE-Size
            sal_uInt8 nbyte(0);
            pOS2MET->ReadUChar( nbyte ); p->nBitsPerPixel=nbyte;
            break;
        }

        case 0x0097:   // Image LUT-ID
            break;

        case 0x009b:   // IDE Structure
            break;

        case 0xfe92: { // Image Data
            // At the latest we now need the temporary BMP file and
            // inside this file we need the header and the palette.
            if (p->pBMP==nullptr) {
                p->pBMP=new SvMemoryStream();
                p->pBMP->SetEndian(SvStreamEndian::LITTLE);
                if (p->nWidth==0 || p->nHeight==0 || p->nBitsPerPixel==0) {
                    pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                    ErrorCode=3;
                    return;
                }
                // write (Windows-)BITMAPINFOHEADER:
                p->pBMP->WriteUInt32( 40 ).WriteUInt32( p->nWidth ).WriteUInt32( p->nHeight );
                p->pBMP->WriteUInt16( 1 ).WriteUInt16( p->nBitsPerPixel );
                p->pBMP->WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 ).WriteUInt32( 0 );
                p->pBMP->WriteUInt32( 0 ).WriteUInt32( 0 );
                // write color table:
                if (p->nBitsPerPixel<=8) {
                    sal_uInt16 i, nColTabSize=1<<(p->nBitsPerPixel);
                    for (i=0; i<nColTabSize; i++) p->pBMP->WriteUInt32( GetPalette0RGB(i) );
                }
            }
            // OK, now the map data is being pushed. Unfortunately OS2 and BMP
            // do have a different RGB ordering when using 24-bit
            std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[nDataLen]);
            pOS2MET->ReadBytes(pBuf.get(), nDataLen);
            sal_uInt32 nBytesPerLineToSwap = (p->nBitsPerPixel == 24) ?
                ((p->nWidth * 3 + 3) & 0xfffffffc) : 0;
            if (nBytesPerLineToSwap) {
                sal_uInt32 nAlign = p->nMapPos - (p->nMapPos % nBytesPerLineToSwap);
                sal_uInt32 i=0;
                while (nAlign+i+2<p->nMapPos+nDataLen) {
                    if (nAlign+i>=p->nMapPos) {
                        sal_uInt32 j = nAlign + i - p->nMapPos;
                        std::swap(pBuf[j], pBuf[j+2]);
                    }
                    i+=3;
                    if (i + 2 >= nBytesPerLineToSwap) {
                        nAlign += nBytesPerLineToSwap;
                        i=0;
                    }
                }
            }
            p->pBMP->WriteBytes(pBuf.get(), nDataLen);
            p->nMapPos+=nDataLen;
            break;
        }
        case 0x0093:   // End Image Content
            break;

        case 0x0071:   // End Segment
            break;
    }
}

void OS2METReader::ReadFont(sal_uInt16 nFieldSize)
{
    OSFont * pF=new OSFont;
    pF->pSucc=pFontList; pFontList=pF;
    pF->nID=0;
    pF->aFont.SetTransparent(true);
    pF->aFont.SetAlignment(ALIGN_BASELINE);

    auto nPos=pOS2MET->Tell();
    auto nMaxPos = nPos + nFieldSize;
    pOS2MET->SeekRel(2); nPos+=2;
    while (nPos<nMaxPos && pOS2MET->good()) {
        sal_uInt8 nByte(0);
        pOS2MET->ReadUChar(nByte);
        sal_uInt16 nLen = static_cast<sal_uInt16>(nByte) & 0x00ff;
        if (nLen == 0)
        {
            pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
            ErrorCode=4;
        }
        sal_uInt8 nTripType(0);
        pOS2MET->ReadUChar( nTripType );
        switch (nTripType) {
            case 0x02:
            {
                sal_uInt8 nTripType2(0);
                pOS2MET->ReadUChar( nTripType2 );
                switch (nTripType2) {
                    case 0x84:   // Font name
                        break;
                    case 0x08: { // Font Typeface
                        char str[33];
                        pOS2MET->SeekRel(1);
                        str[pOS2MET->ReadBytes(str, 32)] = 0;
                        OUString aStr( str, strlen(str), osl_getThreadTextEncoding() );
                        if ( aStr.equalsIgnoreAsciiCase( "Helv" ) )
                            aStr = "Helvetica";
                        pF->aFont.SetFamilyName( aStr );
                        break;
                    }
                }
                break;
            }
            case 0x24:   // Icid
            {
                sal_uInt8 nTripType2(0);
                pOS2MET->ReadUChar( nTripType2 );
                switch (nTripType2) {
                    case 0x05:   //Icid
                        pOS2MET->ReadUChar( nByte );
                        pF->nID=static_cast<sal_uInt32>(nByte)&0xff;
                        break;
                }
                break;
            }
            case 0x20:   // Font Binary GCID
                break;
            case 0x1f: { // Font Attributes
                FontWeight eWeight;
                sal_uInt8 nbyte(0);
                pOS2MET->ReadUChar( nbyte );
                switch (nbyte) {
                    case 1:  eWeight=WEIGHT_THIN;       break;
                    case 2:  eWeight=WEIGHT_ULTRALIGHT; break;
                    case 3:  eWeight=WEIGHT_LIGHT;      break;
                    case 4:  eWeight=WEIGHT_SEMILIGHT;  break;
                    case 5:  eWeight=WEIGHT_NORMAL;     break;
                    case 6:  eWeight=WEIGHT_SEMIBOLD;   break;
                    case 7:  eWeight=WEIGHT_BOLD;       break;
                    case 8:  eWeight=WEIGHT_ULTRABOLD;  break;
                    case 9:  eWeight=WEIGHT_BLACK;      break;
                    default: eWeight=WEIGHT_DONTKNOW;
                }
                pF->aFont.SetWeight(eWeight);
                break;
            }
        }
        nPos+=nLen;
        pOS2MET->Seek(nPos);
    }
}

void OS2METReader::ReadField(sal_uInt16 nFieldType, sal_uInt16 nFieldSize)
{
    switch (nFieldType) {
        case BegDocumnMagic:
            break;
        case EndDocumnMagic:
            break;
        case BegResGrpMagic:
            break;
        case EndResGrpMagic:
            break;
        case BegColAtrMagic:
            break;
        case EndColAtrMagic:
            break;
        case BlkColAtrMagic: {
            sal_uInt8 nbyte;
            sal_uInt16 nStartIndex, nEndIndex, i, nElemLen, nBytesPerCol;

            auto nPos = pOS2MET->Tell();
            auto nMaxPos = nPos + nFieldSize;
            pOS2MET->SeekRel(3); nPos+=3;
            while (nPos<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
                pOS2MET->ReadUChar( nbyte ); nElemLen=static_cast<sal_uInt16>(nbyte) & 0x00ff;
                if (nElemLen>11) {
                    pOS2MET->SeekRel(4);
                    nStartIndex=ReadBigEndianWord();
                    pOS2MET->SeekRel(3);
                    pOS2MET->ReadUChar( nbyte );
                    nBytesPerCol=static_cast<sal_uInt16>(nbyte) & 0x00ff;
                    if (nBytesPerCol == 0)
                    {
                        pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                        ErrorCode=4;
                        break;
                    }
                    nEndIndex=nStartIndex+(nElemLen-11)/nBytesPerCol;
                    for (i=nStartIndex; i<nEndIndex; i++) {
                        if (nBytesPerCol > 3) pOS2MET->SeekRel(nBytesPerCol-3);
                        auto nCol = ReadBigEndian3BytesLong();
                        SetPalette0RGB(i, nCol);
                    }
                }
                else if (nElemLen<10) {
                    pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                    ErrorCode=4;
                }
                nPos += nElemLen;
                pOS2MET->Seek(nPos);
            }
            break;
        }
        case MapColAtrMagic:
            break;
        case BegImgObjMagic: {
            // create new bitmap by now: (will be filled later)
            OSBitmap * pB=new OSBitmap;
            pB->pSucc=pBitmapList; pBitmapList=pB;
            pB->pBMP=nullptr; pB->nWidth=0; pB->nHeight=0; pB->nBitsPerPixel=0;
            pB->nMapPos=0;
            // determine ID of the bitmap:
            pB->nID=0;
            for (sal_uInt8 i = 0; i < 4; ++i) {
                sal_uInt8 nbyte(0),nbyte2(0);
                pOS2MET->ReadUChar(nbyte).ReadUChar(nbyte2);
                nbyte = sal_uInt8(nbyte - 0x30);
                nbyte2 = sal_uInt8(nbyte2 - 0x30);
                nbyte = (nbyte << 4) | nbyte2;
                pB->nID=(pB->nID>>8)|(static_cast<sal_uInt32>(nbyte)<<24);
            }
            // put new palette on the palette stack: (will be filled later)
            OSPalette * pP=new OSPalette;
            pP->pSucc=pPaletteStack; pPaletteStack=pP;
            pP->p0RGB=nullptr; pP->nSize=0;
            break;
        }
        case EndImgObjMagic: {
            // read temporary Windows BMP file:
            if (pBitmapList==nullptr || pBitmapList->pBMP==nullptr ||
                pBitmapList->pBMP->GetError()!=ERRCODE_NONE) {
                pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                ErrorCode=5;
                return;
            }
            pBitmapList->pBMP->Seek(0);

            ReadDIBBitmapEx(pBitmapList->aBitmap, *(pBitmapList->pBMP), false);

            if (pBitmapList->pBMP->GetError()!=ERRCODE_NONE) {
                pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                ErrorCode=6;
            }
            delete pBitmapList->pBMP; pBitmapList->pBMP=nullptr;
            // kill palette from stack:
            OSPalette * pP=pPaletteStack;
            if (pP!=nullptr) {
                pPaletteStack=pP->pSucc;
                delete[] pP->p0RGB;
                delete pP;
            }
            break;
        }
        case DscImgObjMagic:
            break;
        case DatImgObjMagic: {
            sal_uInt16 nDataID, nDataLen;
            sal_uInt8 nbyte;

            auto nPos = pOS2MET->Tell();
            auto nMaxPos = nPos + nFieldSize;
            while (nPos<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
                pOS2MET->ReadUChar( nbyte ); nDataID=static_cast<sal_uInt16>(nbyte)&0x00ff;
                if (nDataID==0x00fe) {
                    pOS2MET->ReadUChar( nbyte );
                    nDataID=(nDataID<<8)|(static_cast<sal_uInt16>(nbyte)&0x00ff);
                    nDataLen=ReadBigEndianWord();
                    nPos+=4;
                }
                else {
                    pOS2MET->ReadUChar( nbyte ); nDataLen=static_cast<sal_uInt16>(nbyte)&0x00ff;
                    nPos+=2;
                }
                ReadImageData(nDataID, nDataLen);
                nPos += nDataLen;
                pOS2MET->Seek(nPos);
            }
            break;
        }

        case BegObEnv1Magic:
            break;
        case EndObEnv1Magic:
            break;
        case BegGrfObjMagic:
            break;
        case EndGrfObjMagic: {
            if (!xOrdFile)
                break;

            auto nMaxPos = xOrdFile->Tell();
            if (!nMaxPos)
                break;

            // In xOrdFile all "DatGrfObj" fields were collected so that the
            // therein contained "Orders" are continuous and not segmented by fields.
            // To read them from the memory stream without having any trouble,
            // we use a  little trick:

            SvStream *pSave = pOS2MET;
            pOS2MET=xOrdFile.get(); //(!)
            pOS2MET->Seek(0);

            // in a sane world this block is just: pOS2MET->SetStreamSize(nMaxPos);
            if (nMaxPos)
            {
#ifndef NDEBUG
                const sal_uInt8 nLastByte = static_cast<const sal_uInt8*>(xOrdFile->GetData())[nMaxPos-1];
#endif
                pOS2MET->SetStreamSize(nMaxPos); // shrink stream to written portion
                assert(pOS2MET->remainingSize() == nMaxPos || pOS2MET->remainingSize() == nMaxPos - 1);
                SAL_WARN_IF(pOS2MET->remainingSize() == nMaxPos, "filter.os2met", "this SetStreamSize workaround is no longer needed");
                // The shrink case of SvMemoryStream::ReAllocateMemory, i.e. nEndOfData = nNewSize - 1, looks buggy to me, workaround
                // it by using Seek to move the nEndOfData to the sane position
                if (pOS2MET->remainingSize() < nMaxPos)
                {
                    pOS2MET->Seek(nMaxPos);
                    pOS2MET->Seek(0);
                }

                assert(nLastByte == static_cast<const sal_uInt8*>(xOrdFile->GetData())[nMaxPos-1]);
            }

            assert(pOS2MET->remainingSize() == nMaxPos);

            // disable stream growing past its current size
            xOrdFile->SetResizeOffset(0);


            // "Segment header":
            sal_uInt8 nbyte(0);
            pOS2MET->ReadUChar( nbyte );
            if (nbyte==0x70) { // header exists
                pOS2MET->SeekRel(15); // but we don't need it
            }
            else pOS2MET->SeekRel(-1); // no header, go back one byte

            // loop through Order:
            while (pOS2MET->Tell()<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
                pOS2MET->ReadUChar( nbyte );
                sal_uInt16 nOrderID = static_cast<sal_uInt16>(nbyte) & 0x00ff;
                if (nOrderID==0x00fe) {
                    pOS2MET->ReadUChar( nbyte );
                    nOrderID=(nOrderID << 8) | (static_cast<sal_uInt16>(nbyte) & 0x00ff);
                }
                sal_uInt16 nOrderLen;
                if (nOrderID>0x00ff || nOrderID==GOrdPolygn) {
                    // ooo: As written in OS2 documentation, the order length should now
                    // be written as big endian word. (Quote: "Highorder byte precedes loworder byte").
                    // In reality there are files in which the length is stored as little endian word
                    // (at least for nOrderID==GOrdPolygn)
                    // So we throw a coin or what else can we do?
                    pOS2MET->ReadUChar( nbyte ); nOrderLen=static_cast<sal_uInt16>(nbyte)&0x00ff;
                    pOS2MET->ReadUChar( nbyte ); if (nbyte!=0) nOrderLen=nOrderLen<<8|(static_cast<sal_uInt16>(nbyte)&0x00ff);
                }
                else if (nOrderID==GOrdSTxAlg || nOrderID==GOrdPTxAlg) nOrderLen=2;
                else if ((nOrderID&0xff88)==0x0008) nOrderLen=1;
                else if (nOrderID==0x0000 || nOrderID==0x00ff) nOrderLen=0;
                else { pOS2MET->ReadUChar( nbyte ); nOrderLen=static_cast<sal_uInt16>(nbyte) & 0x00ff; }
                auto nPos=pOS2MET->Tell();
                ReadOrder(nOrderID, nOrderLen);
                if (nPos+nOrderLen < pOS2MET->Tell()) {
                    SAL_INFO("filter.os2met","Order is shorter than expected. OrderID: " << nOrderID << " Position: " << nPos);
                }
                else if (nPos+nOrderLen != pOS2MET->Tell()) {
                    SAL_INFO("filter.os2met","Order was not read completely. OrderID: " << nOrderID << " Position: " << nPos);
                }
                pOS2MET->Seek(nPos+nOrderLen);
            }

            pOS2MET=pSave;
            if (xOrdFile->GetError()) {
                pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
                ErrorCode=10;
            }
            xOrdFile.reset();
            break;
        }
        case DscGrfObjMagic: {
            sal_uInt16 nDscID, nDscLen;
            sal_uInt8 nbyte;

            auto nMaxPos = pOS2MET->Tell() + nFieldSize;
            while (pOS2MET->Tell()<nMaxPos && pOS2MET->GetError()==ERRCODE_NONE) {
                pOS2MET->ReadUChar( nbyte ); nDscID =static_cast<sal_uInt16>(nbyte) & 0x00ff;
                pOS2MET->ReadUChar( nbyte ); nDscLen=static_cast<sal_uInt16>(nbyte) & 0x00ff;
                auto nPos = pOS2MET->Tell();
                ReadDsc(nDscID);
                pOS2MET->Seek(nPos+nDscLen);
            }
            break;
        }
        case DatGrfObjMagic: {
            if (!xOrdFile) {
                xOrdFile.reset(new SvMemoryStream);
                xOrdFile->SetEndian(SvStreamEndian::LITTLE);
            }
            std::unique_ptr<sal_uInt8[]> pBuf(new sal_uInt8[nFieldSize]);
            pOS2MET->ReadBytes(pBuf.get(), nFieldSize);
            xOrdFile->WriteBytes(pBuf.get(), nFieldSize);
            break;
        }
        case MapCodFntMagic:
            ReadFont(nFieldSize);
            break;

        case MapDatResMagic:
            break;
    }
}

void OS2METReader::ReadOS2MET( SvStream & rStreamOS2MET, GDIMetaFile & rGDIMetaFile )
{
    ErrorCode=0;

    pOS2MET = &rStreamOS2MET;
    auto nOrigPos = pOS2MET->Tell();
    SvStreamEndian nOrigNumberFormat = pOS2MET->GetEndian();

    bCoord32 = true;
    pPaletteStack=nullptr;
    pAreaStack=nullptr;
    pPathStack=nullptr;
    pPathList=nullptr;
    pFontList=nullptr;
    pBitmapList=nullptr;
    pAttrStack=nullptr;

    aDefAttr.aLinCol     =COL_BLACK;
    aDefAttr.aLinBgCol   =COL_WHITE;
    aDefAttr.eLinMix     =RasterOp::OverPaint;
    aDefAttr.eLinBgMix   =RasterOp::OverPaint;
    aDefAttr.aChrCol     =COL_BLACK;
    aDefAttr.aChrBgCol   =COL_WHITE;
    aDefAttr.eChrMix     =RasterOp::OverPaint;
    aDefAttr.eChrBgMix   =RasterOp::OverPaint;
    aDefAttr.aMrkCol     =COL_BLACK;
    aDefAttr.aMrkBgCol   =COL_WHITE;
    aDefAttr.eMrkMix     =RasterOp::OverPaint;
    aDefAttr.eMrkBgMix   =RasterOp::OverPaint;
    aDefAttr.aPatCol     =COL_BLACK;
    aDefAttr.aPatBgCol   =COL_WHITE;
    aDefAttr.ePatMix     =RasterOp::OverPaint;
    aDefAttr.ePatBgMix   =RasterOp::OverPaint;
    aDefAttr.aImgCol     =COL_BLACK;
    aDefAttr.aImgBgCol   =COL_WHITE;
    aDefAttr.eImgMix     =RasterOp::OverPaint;
    aDefAttr.eImgBgMix   =RasterOp::OverPaint;
    aDefAttr.nArcP       =1;
    aDefAttr.nArcQ       =1;
    aDefAttr.nArcR       =0;
    aDefAttr.nArcS       =0;
    aDefAttr.nChrAng     =0_deg10;
    aDefAttr.nChrCellHeight = 12;
    aDefAttr.nChrSet     =0;
    aDefAttr.aCurPos     =Point(0,0);
    aDefAttr.eLinStyle   =PEN_SOLID;
    aDefAttr.nLinWidth   =0;
    aDefAttr.aMrkCellSize=Size(10,10);
    aDefAttr.nMrkPrec    =0x01;
    aDefAttr.nMrkSet     =0xff;
    aDefAttr.nMrkSymbol  =0x01;
    aDefAttr.bFill       =true;
    aDefAttr.nStrLinWidth=0;

    aAttr=aDefAttr;

    xOrdFile.reset();

    rGDIMetaFile.Record(pVirDev);

    pOS2MET->SetEndian(SvStreamEndian::LITTLE);

    sal_uInt64 nPos = pOS2MET->Tell();

    for (;;) {

        sal_uInt16 nFieldSize = ReadBigEndianWord();
        sal_uInt8 nMagicByte(0);
        pOS2MET->ReadUChar( nMagicByte );
        if (nMagicByte!=0xd3) {
            pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
            ErrorCode=7;
            break;
        }

        sal_uInt16 nFieldType(0);
        pOS2MET->ReadUInt16(nFieldType);

        pOS2MET->SeekRel(3);

        if (pOS2MET->GetError())
            break;

        if (nFieldType==EndDocumnMagic)
            break;

        if (pOS2MET->eof() || nFieldSize < 8)
        {
            pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
            ErrorCode=8;
            break;
        }

        nPos+=8; nFieldSize-=8;

        if (nFieldSize > pOS2MET->remainingSize())
        {
            pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
            ErrorCode=8;
            break;
        }

        ReadField(nFieldType, nFieldSize);
        nPos += nFieldSize;

        if (pOS2MET->Tell() > nPos)
        {
            pOS2MET->SetError(SVSTREAM_FILEFORMAT_ERROR);
            ErrorCode=9;
            break;
        }
        pOS2MET->Seek(nPos);
    }

    rGDIMetaFile.Stop();

    rGDIMetaFile.SetPrefMapMode( aGlobMapMode );

    if( aBoundingRect.GetWidth() && aBoundingRect.GetHeight() )
        rGDIMetaFile.SetPrefSize( aBoundingRect.GetSize() );
    else
    {
        if( aCalcBndRect.Left() || aCalcBndRect.Top() )
            rGDIMetaFile.Move( -aCalcBndRect.Left(), -aCalcBndRect.Top() );

        rGDIMetaFile.SetPrefSize( aCalcBndRect.GetSize() );
    }

    pOS2MET->SetEndian(nOrigNumberFormat);

    if (pOS2MET->GetError()) {
        SAL_INFO("filter.os2met","Error code: " << ErrorCode);
        pOS2MET->Seek(nOrigPos);
    }
}

//================== GraphicImport - the exported function ================

bool ImportMetGraphic(SvStream& rStream, ImportOutput& rImportOutput)
{
    OS2METReader aOS2METReader;
    GDIMetaFile aMTF;
    bool bRet = false;

    try
    {
        aOS2METReader.ReadOS2MET(rStream, aMTF);

        if (!rStream.GetError())
        {
            rImportOutput.moGDIMetaFile = aMTF;
            bRet = true;
        }
    }
    catch (const css::uno::Exception&)
    {
    }

    return bRet;
}

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