/* This file is part of the KDE libraries
 *
 * SPDX-FileCopyrightText: 1998 Stefan Taferner
 *               2001/2003 thierry lorthiois (lorthioist@wanadoo.fr)
 *               2007-2008 Jan Hambrecht <jaham@gmx.net>
 *               2009-2011 Inge Wallin <inge@lysator.liu.se>
 * With the help of WMF documentation by Caolan Mc Namara
 *
 * SPDX-License-Identifier: LGPL-2.0-only
 */

#include "WmfParser.h"
#include "WmfAbstractBackend.h"

#include <VectorImageDebug.h>

#include <QBuffer>
#include <QByteArray>
#include <QDataStream>
#include <QImage>
#include <QPolygon>

#include <math.h>

#define DEBUG_BBOX 0
#define DEBUG_RECORDS 0

/**
   Namespace for Windows Metafile (WMF) classes
*/
namespace Libwmf
{

#if DEBUG_RECORDS
// Used for debugging of records
static const struct KoWmfFunc {
    const char *name;
} koWmfFunc[] = {
    //                                    index metafunc
    {"end"}, // 0 0x00
    {"setBkColor"}, // 1 0x01
    {"setBkMode"}, // 2 0x02
    {"setMapMode"}, // 3 0x03
    {"setRop"}, // 4 0x04
    {"setRelAbs"}, // 5 0x05
    {"setPolyFillMode"}, // 6 0x06
    {"setStretchBltMode"}, // 7 0x07
    {"setTextCharExtra"}, // 8 0x08
    {"setTextColor"}, // 9 0x09
    {"setTextJustification"}, // 10 0x0a
    {"setWindowOrg"}, // 11 0x0b
    {"setWindowExt"}, // 12 0x0c
    {"setViewportOrg"}, // 13 0x0d
    {"setViewportExt"}, // 14 0x0e
    {"offsetWindowOrg"}, // 15 0x0f
    {"scaleWindowExt"}, // 16 0x10
    {"offsetViewportOrg"}, // 17 0x11
    {"scaleViewportExt"}, // 18 0x12
    {"lineTo"}, // 19 0x13
    {"moveTo"}, // 20 0x14
    {"excludeClipRect"}, // 21 0x15
    {"intersectClipRect"}, // 22 0x16
    {"arc"}, // 23 0x17
    {"ellipse"}, // 24 0x18
    {"floodfill"}, // 25 0x19  floodfill
    {"pie"}, // 26 0x1a
    {"rectangle"}, // 27 0x1b
    {"roundRect"}, // 28 0x1c
    {"patBlt"}, // 29 0x1d
    {"saveDC"}, // 30 0x1e
    {"setPixel"}, // 31 0x1f
    {"offsetClipRegion"}, // 32 0x20
    {"textOut"}, // 33 0x21
    {"bitBlt"}, // 34 0x22
    {"stretchBlt"}, // 35 0x23
    {"polygon"}, // 36 0x24
    {"polyline"}, // 37 0x25
    {"escape"}, // 38 0x26
    {"restoreDC"}, // 39 0x27
    {"fillRegion"}, // 40 0x28
    {"frameRegion"}, // 41 0x29
    {"invertRegion"}, // 42 0x2a
    {"paintRegion"}, // 43 0x2b
    {"selectClipRegion"}, // 44 0x2c
    {"selectObject"}, // 45 0x2d
    {"setTextAlign"}, // 46 0x2e
    {"noSuchRecord"}, // 47 0x2f
    {"chord"}, // 48 0x30
    {"setMapperFlags"}, // 49 0x31
    {"extTextOut"}, // 50 0x32
    {"setDibToDev"}, // 51 0x33
    {"selectPalette"}, // 52 0x34
    {"realizePalette"}, // 53 0x35
    {"animatePalette"}, // 54 0x36
    {"setPalEntries"}, // 55 0x37
    {"polyPolygon"}, // 56 0x38
    {"resizePalette"}, // 57 0x39
    {"noSuchRecord"}, // 58 0x3a
    {"noSuchRecord"}, // 59 0x3b
    {"noSuchRecord"}, // 60 0x3c
    {"noSuchRecord"}, // 61 0x3d
    {"noSuchRecord"}, // 62 0x3e
    {"unimplemented"}, // 63 0x3f
    {"dibBitBlt"}, // 64 0x40
    {"dibStretchBlt"}, // 65 0x41
    {"dibCreatePatternBrush"}, // 66 0x42
    {"stretchDib"}, // 67 0x43
    {"noSuchRecord"}, // 68 0x44
    {"noSuchRecord"}, // 69 0x45
    {"noSuchRecord"}, // 70 0x46
    {"noSuchRecord"}, // 71 0x47
    {"extFloodFill"}, // 72 0x48
    {"setLayout"}, // 73 0x49
    {"unimplemented"}, // 74 0x4a
    {"unimplemented"}, // 75 0x4b
    {"resetDC"}, // 76 0x4c
    {"startDoc"}, // 77 0x4d
    {"unimplemented"}, // 78 0x4e
    {"startPage"}, // 79 0x4f
    {"endPage"}, // 80 0x50
    {"unimplemented"}, // 81 0x51
    {"unimplemented"}, // 82 0x52
    {"unimplemented"}, // 83 0x53
    {"unimplemented"}, // 84 0x54
    {"unimplemented"}, // 85 0x55
    {"unimplemented"}, // 86 0x56
    {"unimplemented"}, // 87 0x57
    {"unimplemented"}, // 88 0x58
    {"unimplemented"}, // 89 0x59
    {"unimplemented"}, // 90 0x5a
    {"unimplemented"}, // 91 0x5b
    {"unimplemented"}, // 92 0x5c
    {"unimplemented"}, // 93 0x5d
    {"endDoc"}, // 94 0x5e
    {"unimplemented"}, // 95 0x5f
    {"deleteObject"}, // 96 0xf0
    {"noSuchRecord"}, // 97 0xf1
    {"noSuchRecord"}, // 98 0xf2
    {"noSuchRecord"}, // 99 0xf3
    {"noSuchRecord"}, // 100 0xf4
    {"noSuchRecord"}, // 101 0xf5
    {"noSuchRecord"}, // 102 0xf6
    {"createPalette"}, // 103 0xf7
    {"createBrush"}, // 104 0xf8
    {"createPatternBrush"}, // 105 0xf9
    {"createPenIndirect"}, // 106 0xfa
    {"createFontIndirect"}, // 107 0xfb
    {"createBrushIndirect"}, // 108 0xfc
    {"createBitmapIndirect"}, // 109 0xfd
    {"createBitmap"}, // 110 0xfe
    {"createRegion"} // 111 0xff
};
#endif

WmfParser::WmfParser()
{
    mNbrFunc = 0;
    mValid = false;
    mStandard = false;
    mPlaceable = false;
    mEnhanced = false;
    mBuffer = nullptr;
    mObjHandleTab = nullptr;
}

WmfParser::~WmfParser()
{
    if (mObjHandleTab != nullptr) {
        for (int i = 0; i < mNbrObject; i++) {
            if (mObjHandleTab[i] != nullptr)
                delete mObjHandleTab[i];
        }
        delete[] mObjHandleTab;
    }
    if (mBuffer != nullptr) {
        mBuffer->close();
        delete mBuffer;
    }
}

bool WmfParser::load(const QByteArray &array)
{
    // delete previous buffer
    if (mBuffer != nullptr) {
        mBuffer->close();
        delete mBuffer;
        mBuffer = nullptr;
    }

    if (array.size() == 0)
        return false;

    // load into buffer
    mBuffer = new QBuffer();
    mBuffer->setData(array);
    mBuffer->open(QIODevice::ReadOnly);

    // read and check the header
    WmfMetaHeader header;
    WmfEnhMetaHeader eheader;
    WmfPlaceableHeader pheader; // Contains a bounding box
    unsigned short checksum;
    int filePos;

    QDataStream stream(mBuffer);
    stream.setByteOrder(QDataStream::LittleEndian);

    mStackOverflow = false;
    mLayout = LAYOUT_LTR;
    mTextColor = Qt::black;
    mMapMode = MM_ANISOTROPIC;

    mValid = false;
    mStandard = false;
    mPlaceable = false;
    mEnhanced = false;

    // Initialize the bounding box.
    // mBBoxTop = 0;           // The default origin is (0, 0).
    // mBBoxLeft = 0;
    mBBoxTop = 32767;
    mBBoxLeft = 32767;
    mBBoxRight = -32768;
    mBBoxBottom = -32768;
    mMaxWidth = 0;
    mMaxHeight = 0;

#if DEBUG_RECORDS
    debugVectorImage << "--------------------------- Starting parsing WMF ---------------------------";
#endif
    stream >> pheader.key;
    if (pheader.key == (quint32)APMHEADER_KEY) {
        //----- Read placeable metafile header
        mPlaceable = true;
#if DEBUG_RECORDS
        debugVectorImage << "Placeable header!  Yessss!";
#endif

        stream >> pheader.handle;
        stream >> pheader.left;
        stream >> pheader.top;
        stream >> pheader.right;
        stream >> pheader.bottom;
        stream >> pheader.inch;
        stream >> pheader.reserved;
        stream >> pheader.checksum;
        checksum = calcCheckSum(&pheader);
        if (pheader.checksum != checksum) {
            warnVectorImage << "Checksum for placeable metafile header is incorrect ( actual checksum" << pheader.checksum << ", expected checksum" << checksum
                            << ")";
            return false;
        }
        stream >> header.fileType;
        stream >> header.headerSize;
        stream >> header.version;
        stream >> header.fileSize;
        stream >> header.numOfObjects;
        stream >> header.maxRecordSize;
        stream >> header.numOfParameters;

        mNbrObject = header.numOfObjects;

        // The bounding box of the WMF
        mBBoxLeft = pheader.left;
        mBBoxTop = pheader.top;
        mBBoxRight = pheader.right;
        mBBoxBottom = pheader.bottom;
#if DEBUG_RECORDS
        debugVectorImage << "bounding box in header: " << mBBoxLeft << mBBoxTop << mBBoxRight << mBBoxBottom << "width, height: " << mBBoxRight - mBBoxLeft
                         << mBBoxBottom - mBBoxTop;
#endif
        mMaxWidth = abs(pheader.right - pheader.left);
        mMaxHeight = abs(pheader.bottom - pheader.top);

        mDpi = pheader.inch;
    } else {
        mBuffer->reset();
        //----- Read as enhanced metafile header
        filePos = mBuffer->pos();
        stream >> eheader.recordType;
        stream >> eheader.recordSize;
        stream >> eheader.boundsLeft;
        stream >> eheader.boundsTop;
        stream >> eheader.boundsRight;
        stream >> eheader.boundsBottom;
        stream >> eheader.frameLeft;
        stream >> eheader.frameTop;
        stream >> eheader.frameRight;
        stream >> eheader.frameBottom;

        stream >> eheader.signature;
        if (eheader.signature == ENHMETA_SIGNATURE) {
            mEnhanced = true;
            stream >> eheader.version;
            stream >> eheader.size;
            stream >> eheader.numOfRecords;
            stream >> eheader.numHandles;
            stream >> eheader.reserved;
            stream >> eheader.sizeOfDescription;
            stream >> eheader.offsetOfDescription;
            stream >> eheader.numPaletteEntries;
            stream >> eheader.widthDevicePixels;
            stream >> eheader.heightDevicePixels;
            stream >> eheader.widthDeviceMM;
            stream >> eheader.heightDeviceMM;
        } else {
            //----- Read as standard metafile header
            mStandard = true;
            mBuffer->seek(filePos);
            stream >> header.fileType;
            stream >> header.headerSize;
            stream >> header.version;
            stream >> header.fileSize;
            stream >> header.numOfObjects;
            stream >> header.maxRecordSize;
            stream >> header.numOfParameters;
            mNbrObject = header.numOfObjects;
        }
    }
    mOffsetFirstRecord = mBuffer->pos();

    //----- Test header validity
    if (((header.headerSize == 9) && (header.numOfParameters == 0)) || (mPlaceable)) {
        // valid wmf file
        mValid = true;
    } else {
        debugVectorImage << "WmfParser : incorrect file format !";
    }

    // check bounding rectangle for standard meta file
    if (mStandard && mValid) {
        // Note that this call can change mValid.
        createBoundingBox(stream);

#if DEBUG_RECORDS
        debugVectorImage << "bounding box created by going through all records: " << mBBoxLeft << mBBoxTop << mBBoxRight << mBBoxBottom
                         << "width, height: " << mBBoxRight - mBBoxLeft << mBBoxBottom - mBBoxTop;
#endif
    }

    return mValid;
}

bool WmfParser::play(WmfAbstractBackend *backend)
{
    if (!(mValid)) {
        debugVectorImage << "WmfParser::play : invalid WMF file";
        return false;
    }

    if (mNbrFunc) {
#if DEBUG_RECORDS
        if ((mStandard)) {
            debugVectorImage << "Standard :" << mBBoxLeft << "" << mBBoxTop << "" << mBBoxRight - mBBoxLeft << "" << mBBoxBottom - mBBoxTop;
        } else {
            debugVectorImage << "DPI :" << mDpi;
            debugVectorImage << "size (inch):" << (mBBoxRight - mBBoxLeft) / mDpi << "" << (mBBoxBottom - mBBoxTop) / mDpi;
            debugVectorImage << "size (mm):" << (mBBoxRight - mBBoxLeft) * 25.4 / mDpi << "" << (mBBoxBottom - mBBoxTop) * 25.4 / mDpi;
        }
        debugVectorImage << mValid << "" << mStandard << "" << mPlaceable;
#endif
    }

    // Stack of handles
    mObjHandleTab = new KoWmfHandle *[mNbrObject];
    for (int i = 0; i < mNbrObject; i++) {
        mObjHandleTab[i] = nullptr;
    }

    mDeviceContext.reset();

    quint16 recordType;
    quint32 size;
    int bufferOffset;

    // Create a stream from which the records will be read.
    QDataStream stream(mBuffer);
    stream.setByteOrder(QDataStream::LittleEndian);

    // Set the output backend.
    m_backend = backend;

    // Set some initial values.
    mDeviceContext.windowOrg = QPoint(0, 0);
    mDeviceContext.windowExt = QSize(1, 1);

    QRect bbox(QPoint(mBBoxLeft, mBBoxTop), QSize(mBBoxRight - mBBoxLeft, mBBoxBottom - mBBoxTop));
    if (m_backend->begin(bbox)) {
        // Play WMF functions.
        mBuffer->seek(mOffsetFirstRecord);
        recordType = 1;

        while ((recordType) && (!mStackOverflow)) {
            int j = 1;

            bufferOffset = mBuffer->pos();
            stream >> size;
            stream >> recordType;

            // mapping between n function and index of table 'metaFuncTab'
            // lower 8 digits of the function => entry in the table
            quint16 index = recordType & 0xff;
            if (index > 0x5F) {
                index -= 0x90;
            }

#if DEBUG_RECORDS
            debugVectorImage << "Record = " << koWmfFunc[index].name << " (" << hex << recordType << ", index" << dec << index << ")";
#endif

            if (mNbrFunc) {
                // debug mode
                if ((j + 12) > mNbrFunc) {
                    // output last 12 functions
                    int offBuff = mBuffer->pos();
                    quint16 param;

                    debugVectorImage << j << " :" << index << " :";
                    for (quint16 i = 0; i < (size - 3); i++) {
                        stream >> param;
                        debugVectorImage << param << "";
                    }
                    debugVectorImage;
                    mBuffer->seek(offBuff);
                }
                if (j >= mNbrFunc) {
                    break;
                }
                j++;
            }

            // Execute the function and parse the record.
            switch (recordType & 0xff) {
            case (META_EOF & 0xff):
                // Don't need to do anything here.
                break;
            case (META_SETBKCOLOR & 0xff): {
                quint32 color;

                stream >> color;
                mDeviceContext.backgroundColor = qtColor(color);
                mDeviceContext.changedItems |= DCBgTextColor;
            } break;
            case (META_SETBKMODE & 0xff): {
                quint16 bkMode;

                stream >> bkMode;
                // debugVectorImage << "New bkMode: " << bkMode;

                mDeviceContext.bgMixMode = bkMode;
                mDeviceContext.changedItems |= DCBgMixMode;
            } break;
            case (META_SETMAPMODE & 0xff): {
                stream >> mMapMode;
                // debugVectorImage << "New mapmode: " << mMapMode;

                // mDeviceContext.FontMapMode = mMapMode;Not defined yet
                mDeviceContext.changedItems |= DCFontMapMode;
            } break;
            case (META_SETROP2 & 0xff): {
                quint16 rop;

                stream >> rop;
                m_backend->setCompositionMode(winToQtComposition(rop));

                mDeviceContext.rop = rop;
                mDeviceContext.changedItems |= DCFgMixMode;
            } break;
            case (META_SETRELABS & 0xff):
                break;
            case (META_SETPOLYFILLMODE & 0xff): {
                stream >> mDeviceContext.polyFillMode;
                mDeviceContext.changedItems |= DCPolyFillMode;
            } break;
            case (META_SETSTRETCHBLTMODE & 0xff):
            case (META_SETTEXTCHAREXTRA & 0xff):
                break;
            case (META_SETTEXTCOLOR & 0xff): {
                quint32 color;

                stream >> color;
                mDeviceContext.foregroundTextColor = qtColor(color);
                mDeviceContext.changedItems |= DCFgTextColor;
            } break;
            case (META_SETTEXTJUSTIFICATION & 0xff):
                break;
            case (META_SETWINDOWORG & 0xff): {
                qint16 top, left;

                stream >> top >> left;

                m_backend->setWindowOrg(left, top);
                mDeviceContext.windowOrg = QPoint(left, top);
#if DEBUG_RECORDS
                debugVectorImage << "Org: (" << left << "," << top << ")";
#endif
            } break;
            case (META_SETWINDOWEXT & 0xff): {
                qint16 width, height;

                // negative value allowed for width and height
                stream >> height >> width;
#if DEBUG_RECORDS
                debugVectorImage << "Ext: (" << width << "," << height << ")";
#endif

                m_backend->setWindowExt(width, height);
                mDeviceContext.windowExt = QSize(width, height);
            } break;
            case (META_SETVIEWPORTORG & 0xff): {
                qint16 top, left;

                stream >> top >> left;
                m_backend->setViewportOrg(left, top);
                mDeviceContext.viewportOrg = QPoint(left, top);

#if DEBUG_RECORDS
                debugVectorImage << "Org: (" << left << "," << top << ")";
#endif
            } break;
            case (META_SETVIEWPORTEXT & 0xff): {
                qint16 width, height;

                // Negative value allowed for width and height
                stream >> height >> width;
#if DEBUG_RECORDS
                debugVectorImage << "Ext: (" << width << "," << height << ")";
#endif

                m_backend->setViewportExt(width, height);
                mDeviceContext.viewportExt = QSize(width, height);
            } break;
            case (META_OFFSETWINDOWORG & 0xff): {
                qint16 offTop, offLeft;

                stream >> offTop >> offLeft;
                m_backend->setWindowOrg(mDeviceContext.windowOrg.x() + offLeft, mDeviceContext.windowOrg.y() + offTop);

                mDeviceContext.windowOrg += QPoint(offLeft, offTop);
            } break;
            case (META_SCALEWINDOWEXT & 0xff): {
                // Use 32 bits in the calculations to not lose precision.
                qint32 width, height;
                qint16 heightDenum, heightNum, widthDenum, widthNum;

                stream >> heightDenum >> heightNum >> widthDenum >> widthNum;

                if ((widthDenum != 0) && (heightDenum != 0)) {
                    width = (qint32(mDeviceContext.windowExt.width()) * widthNum) / widthDenum;
                    height = (qint32(mDeviceContext.windowExt.height()) * heightNum) / heightDenum;
                    m_backend->setWindowExt(width, height);
                    mDeviceContext.windowExt = QSize(width, height);
                }
                // debugVectorImage <<"WmfParser::ScaleWindowExt :" << widthDenum <<"" << heightDenum;
            } break;
            case (META_OFFSETVIEWPORTORG & 0xff): {
                qint16 offTop, offLeft;

                stream >> offTop >> offLeft;
                m_backend->setViewportOrg(mDeviceContext.windowOrg.x() + offLeft, mDeviceContext.windowOrg.y() + offTop);
                mDeviceContext.viewportOrg += QPoint(offLeft, offTop);
            } break;
            case (META_SCALEVIEWPORTEXT & 0xff): {
                // Use 32 bits in the calculations to not lose precision.
                qint32 width, height;
                qint16 heightDenum, heightNum, widthDenum, widthNum;

                stream >> heightDenum >> heightNum >> widthDenum >> widthNum;

                if ((widthDenum != 0) && (heightDenum != 0)) {
                    width = (qint32(mDeviceContext.windowExt.width()) * widthNum) / widthDenum;
                    height = (qint32(mDeviceContext.windowExt.height()) * heightNum) / heightDenum;
                    m_backend->setViewportExt(width, height);
                    mDeviceContext.viewportExt = QSize(width, height);
                }
                // debugVectorImage <<"WmfParser::ScaleWindowExt :" << widthDenum <<"" << heightDenum;
            } break;

                // ----------------------------------------------------------------
                //                         Drawing records

            case (META_LINETO & 0xff): {
                qint16 top, left;

                stream >> top >> left;
                m_backend->lineTo(mDeviceContext, left, top);
            } break;
            case (META_MOVETO & 0xff): {
                qint16 top, left;

                stream >> top >> left;
                mDeviceContext.currentPosition = QPoint(left, top);
            } break;
            case (META_EXCLUDECLIPRECT & 0xff): {
                qint16 top, left, right, bottom;

                stream >> bottom >> right >> top >> left;

                QRegion region = mDeviceContext.clipRegion;
                QRegion newRegion(left, top, right - left, bottom - top);
                if (region.isEmpty()) {
                    // FIXME: I doubt that if the region is previously empty,
                    //        it should be set to the new region.  /iw
                    region = newRegion;
                } else {
                    region = region.subtracted(newRegion);
                }

                mDeviceContext.clipRegion = region;
                mDeviceContext.changedItems |= DCClipRegion;
            } break;
            case (META_INTERSECTCLIPRECT & 0xff): {
                qint16 top, left, right, bottom;

                stream >> bottom >> right >> top >> left;

                QRegion region = mDeviceContext.clipRegion;
                QRegion newRegion(left, top, right - left, bottom - top);
                if (region.isEmpty()) {
                    // FIXME: I doubt that if the region is previously empty,
                    //        it should be set to the new region.  /iw
                    region = newRegion;
                } else {
                    region = region.intersected(newRegion);
                }

                mDeviceContext.clipRegion = region;
                mDeviceContext.changedItems |= DCClipRegion;
            } break;
            case (META_ARC & 0xff): {
                int xCenter, yCenter, angleStart, aLength;
                qint16 topEnd, leftEnd, topStart, leftStart;
                qint16 top, left, right, bottom;

                stream >> topEnd >> leftEnd >> topStart >> leftStart;
                stream >> bottom >> right >> top >> left;

                xCenter = left + ((right - left) / 2);
                yCenter = top + ((bottom - top) / 2);
                xyToAngle(leftStart - xCenter, yCenter - topStart, leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);

                m_backend->drawArc(mDeviceContext, left, top, right - left, bottom - top, angleStart, aLength);
            } break;
            case (META_ELLIPSE & 0xff): {
                qint16 top, left, right, bottom;

                stream >> bottom >> right >> top >> left;
                m_backend->drawEllipse(mDeviceContext, left, top, right - left, bottom - top);
            } break;
            case (META_FLOODFILL & 0xff):
                break;
            case (META_PIE & 0xff): {
                int xCenter, yCenter, angleStart, aLength;
                qint16 topEnd, leftEnd, topStart, leftStart;
                qint16 top, left, right, bottom;

                stream >> topEnd >> leftEnd >> topStart >> leftStart;
                stream >> bottom >> right >> top >> left;

                xCenter = left + ((right - left) / 2);
                yCenter = top + ((bottom - top) / 2);
                xyToAngle(leftStart - xCenter, yCenter - topStart, leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);

                m_backend->drawPie(mDeviceContext, left, top, right - left, bottom - top, angleStart, aLength);
            } break;
            case (META_RECTANGLE & 0xff): {
                qint16 top, left, right, bottom;

                stream >> bottom >> right >> top >> left;
                // debugVectorImage << left << top << right << bottom;
                m_backend->drawRect(mDeviceContext, left, top, right - left, bottom - top);
            } break;
            case (META_ROUNDRECT & 0xff): {
                int xRnd = 0, yRnd = 0;
                quint16 widthCorner, heightCorner;
                qint16 top, left, right, bottom;

                stream >> heightCorner >> widthCorner;
                stream >> bottom >> right >> top >> left;

                // convert (widthCorner, heightCorner) in percentage
                if ((right - left) != 0)
                    xRnd = (widthCorner * 100) / (right - left);
                if ((bottom - top) != 0)
                    yRnd = (heightCorner * 100) / (bottom - top);

                m_backend->drawRoundRect(mDeviceContext, left, top, right - left, bottom - top, xRnd, yRnd);
            } break;
            case (META_PATBLT & 0xff): {
                quint32 rasterOperation;
                quint16 height, width;
                qint16 y, x;

                stream >> rasterOperation;
                stream >> height >> width;
                stream >> y >> x;

                // debugVectorImage << "patBlt record" << hex << rasterOperation << dec
                //               << x << y << width << height;

                m_backend->patBlt(mDeviceContext, x, y, width, height, rasterOperation);
            } break;
            case (META_SAVEDC & 0xff):
                m_backend->save();
                break;
            case (META_SETPIXEL & 0xff): {
                qint16 left, top;
                quint32 color;

                stream >> color >> top >> left;
                m_backend->setPixel(mDeviceContext, left, top, qtColor(color));
            } break;
            case (META_OFFSETCLIPRGN & 0xff):
                break;
            case (META_TEXTOUT & 0xff): {
                quint16 textLength;
                qint16 x, y;

                stream >> textLength;

                QByteArray text;
                text.resize(textLength);
                stream.readRawData(text.data(), textLength);

                // The string is always of even length, so if the actual data is
                // of uneven length, read an extra byte.
                if (textLength & 0x01) {
                    quint8 dummy;
                    stream >> dummy;
                }

                stream >> y;
                stream >> x;

                m_backend->drawText(mDeviceContext, x, y, text);
            } break;
            case (META_BITBLT & 0xff):
            case (META_STRETCHBLT & 0xff):
                break;
            case (META_POLYGON & 0xff): {
                quint16 num;

                stream >> num;

                QPolygon pa(num);

                pointArray(stream, pa);
                m_backend->drawPolygon(mDeviceContext, pa);
            } break;
            case (META_POLYLINE & 0xff): {
                quint16 num;

                stream >> num;
                QPolygon pa(num);

                pointArray(stream, pa);
                m_backend->drawPolyline(mDeviceContext, pa);
            } break;
            case (META_ESCAPE & 0xff):
                break;
            case (META_RESTOREDC & 0xff): {
                qint16 num;

                stream >> num;
                for (int i = 0; i > num; i--)
                    m_backend->restore();
            } break;
            case (META_FILLREGION & 0xff):
            case (META_FRAMEREGION & 0xff):
            case (META_INVERTREGION & 0xff):
            case (META_PAINTREGION & 0xff):
            case (META_SELECTCLIPREGION & 0xff):
                break;
            case (META_SELECTOBJECT & 0xff): {
                quint16 idx;

                stream >> idx;
                if ((idx < mNbrObject) && (mObjHandleTab[idx] != nullptr))
                    mObjHandleTab[idx]->apply(&mDeviceContext);
                else
                    debugVectorImage << "WmfParser::selectObject : selection of an empty object";
            } break;
            case (META_SETTEXTALIGN & 0xff):
                stream >> mDeviceContext.textAlign;
                mDeviceContext.changedItems |= DCTextAlignMode;
                break;
            case (META_CHORD & 0xff): {
                int xCenter, yCenter, angleStart, aLength;
                qint16 topEnd, leftEnd, topStart, leftStart;
                qint16 top, left, right, bottom;

                stream >> topEnd >> leftEnd >> topStart >> leftStart;
                stream >> bottom >> right >> top >> left;

                xCenter = left + ((right - left) / 2);
                yCenter = top + ((bottom - top) / 2);
                xyToAngle(leftStart - xCenter, yCenter - topStart, leftEnd - xCenter, yCenter - topEnd, angleStart, aLength);

                m_backend->drawChord(mDeviceContext, left, top, right - left, bottom - top, angleStart, aLength);
            } break;
            case (META_SETMAPPERFLAGS & 0xff):
                break;
            case (META_EXTTEXTOUT & 0xff): {
                qint16 y, x;
                qint16 stringLength;
                quint16 fwOpts;
                qint16 top, left, right, bottom; // optional cliprect

                stream >> y;
                stream >> x;
                stream >> stringLength;
                stream >> fwOpts;

                // ETO_CLIPPED flag adds 4 parameters
                if (fwOpts & (ETO_CLIPPED | ETO_OPAQUE)) {
                    // read the optional clip rect
                    stream >> bottom >> right >> top >> left;
                }

                // Read the string. Note that it's padded to 16 bits.
                QByteArray text;
                text.resize(stringLength);
                stream.readRawData(text.data(), stringLength);

                if (stringLength & 0x01) {
                    quint8 padding;
                    stream >> padding;
                }

#if DEBUG_RECORDS
                debugVectorImage << "text at" << x << y << "length" << stringLength << ':' << text;
                // debugVectorImage << "flags:" << hex << fwOpts << dec;
                debugVectorImage << "flags:" << fwOpts;
                debugVectorImage << "record length:" << size;
#endif
                m_backend->drawText(mDeviceContext, x, y, text);
            } break;
            case (META_SETDIBTODEV & 0xff):
            case (META_SELECTPALETTE & 0xff):
            case (META_REALIZEPALETTE & 0xff):
            case (META_ANIMATEPALETTE & 0xff):
            case (META_SETPALENTRIES & 0xff):
                break;
            case (META_POLYPOLYGON & 0xff): {
                quint16 numberPoly;
                quint16 sizePoly;
                QList<QPolygon> listPa;

                stream >> numberPoly;

                for (int i = 0; i < numberPoly; i++) {
                    stream >> sizePoly;
                    listPa.append(QPolygon(sizePoly));
                }

                // list of point array
                for (int i = 0; i < numberPoly; i++) {
                    pointArray(stream, listPa[i]);
                }

                // draw polygon's
                m_backend->drawPolyPolygon(mDeviceContext, listPa);
                listPa.clear();
            } break;
            case (META_RESIZEPALETTE & 0xff):
                break;
            case (META_DIBBITBLT & 0xff): {
                quint32 raster;
                qint16 topSrc, leftSrc, widthSrc, heightSrc;
                qint16 topDst, leftDst;

                stream >> raster;
                stream >> topSrc >> leftSrc >> heightSrc >> widthSrc;
                stream >> topDst >> leftDst;

                if (size > 11) { // DIB image
                    QImage bmpSrc;

                    if (dibToBmp(bmpSrc, stream, (size - 11) * 2)) {
                        m_backend->setCompositionMode(winToQtComposition(raster));

                        m_backend->save();
                        if (widthSrc < 0) {
                            // negative width => horizontal flip
                            QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
                            m_backend->setTransform(mDeviceContext, m, true);
                        }
                        if (heightSrc < 0) {
                            // negative height => vertical flip
                            QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
                            m_backend->setTransform(mDeviceContext, m, true);
                        }
                        m_backend->drawImage(mDeviceContext, leftDst, topDst, bmpSrc, leftSrc, topSrc, widthSrc, heightSrc);
                        m_backend->restore();
                    }
                } else {
                    debugVectorImage << "WmfParser::dibBitBlt without image not implemented";
                }
            } break;
            case (META_DIBSTRETCHBLT & 0xff): {
                quint32 raster;
                qint16 topSrc, leftSrc, widthSrc, heightSrc;
                qint16 topDst, leftDst, widthDst, heightDst;
                QImage bmpSrc;

                stream >> raster;
                stream >> heightSrc >> widthSrc >> topSrc >> leftSrc;
                stream >> heightDst >> widthDst >> topDst >> leftDst;

                if (dibToBmp(bmpSrc, stream, (size - 13) * 2)) {
                    m_backend->setCompositionMode(winToQtComposition(raster));

                    m_backend->save();
                    if (widthDst < 0) {
                        // negative width => horizontal flip
                        QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
                        m_backend->setTransform(mDeviceContext, m, true);
                    }
                    if (heightDst < 0) {
                        // negative height => vertical flip
                        QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
                        m_backend->setTransform(mDeviceContext, m, true);
                    }
                    bmpSrc = bmpSrc.copy(leftSrc, topSrc, widthSrc, heightSrc);
                    // TODO: scale the bitmap : QImage::scale(widthDst, heightDst)
                    // is actually too slow

                    m_backend->drawImage(mDeviceContext, leftDst, topDst, bmpSrc);
                    m_backend->restore();
                }
            } break;
            case (META_DIBCREATEPATTERNBRUSH & 0xff): {
                KoWmfPatternBrushHandle *handle = new KoWmfPatternBrushHandle;

                if (addHandle(handle)) {
                    quint32 arg;
                    QImage bmpSrc;

                    stream >> arg;
                    if (dibToBmp(bmpSrc, stream, (size - 5) * 2)) {
                        handle->image = bmpSrc;
                        handle->brush.setTextureImage(handle->image);
                    } else {
                        debugVectorImage << "WmfParser::dibCreatePatternBrush : incorrect DIB image";
                    }
                }
            } break;
            case (META_STRETCHDIB & 0xff): {
                quint32 raster;
                qint16 arg, topSrc, leftSrc, widthSrc, heightSrc;
                qint16 topDst, leftDst, widthDst, heightDst;
                QImage bmpSrc;

                stream >> raster >> arg;
                stream >> heightSrc >> widthSrc >> topSrc >> leftSrc;
                stream >> heightDst >> widthDst >> topDst >> leftDst;

                if (dibToBmp(bmpSrc, stream, (size - 14) * 2)) {
                    m_backend->setCompositionMode(winToQtComposition(raster));

                    m_backend->save();
                    if (widthDst < 0) {
                        // negative width => horizontal flip
                        QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
                        m_backend->setTransform(mDeviceContext, m, true);
                    }
                    if (heightDst < 0) {
                        // negative height => vertical flip
                        QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
                        m_backend->setTransform(mDeviceContext, m, true);
                    }
                    bmpSrc = bmpSrc.copy(leftSrc, topSrc, widthSrc, heightSrc);
                    // TODO: scale the bitmap ( QImage::scale(param[ 8 ], param[ 7 ]) is actually too slow )

                    m_backend->drawImage(mDeviceContext, leftDst, topDst, bmpSrc);
                    m_backend->restore();
                }
            } break;
            case (META_EXTFLOODFILL & 0xff):
                break;
            case (META_SETLAYOUT & 0xff): {
                quint16 layout;
                quint16 reserved;

                // negative value allowed for width and height
                stream >> layout >> reserved;
#if DEBUG_RECORDS
                debugVectorImage << "layout=" << layout;
#endif
                mLayout = (WmfLayout)layout;

                mDeviceContext.layoutMode = mLayout;
                mDeviceContext.changedItems |= DCLayoutMode;

            } break;
            case (META_DELETEOBJECT & 0xff): {
                quint16 idx;

                stream >> idx;
                deleteHandle(idx);
            } break;
            case (META_CREATEPALETTE & 0xff):
                // Unimplemented
                createEmptyObject();
                break;
            case (META_CREATEBRUSH & 0xff):
            case (META_CREATEPATTERNBRUSH & 0xff):
                break;
            case (META_CREATEPENINDIRECT & 0xff): {
                // TODO: userStyle and alternateStyle
                quint32 color;
                quint16 style, width, arg;

                KoWmfPenHandle *handle = new KoWmfPenHandle;

                if (addHandle(handle)) {
                    stream >> style >> width >> arg >> color;

                    // set the style defaults
                    handle->pen.setStyle(Qt::SolidLine);
                    handle->pen.setCapStyle(Qt::RoundCap);
                    handle->pen.setJoinStyle(Qt::RoundJoin);

                    const int PenStyleMask = 0x0000000F;
                    const int PenCapMask = 0x00000F00;
                    const int PenJoinMask = 0x0000F000;

                    quint16 penStyle = style & PenStyleMask;
                    if (penStyle < 7)
                        handle->pen.setStyle(koWmfStylePen[penStyle]);
                    else
                        debugVectorImage << "WmfParser::createPenIndirect: invalid pen" << style;

                    quint16 capStyle = (style & PenCapMask) >> 8;
                    if (capStyle < 3)
                        handle->pen.setCapStyle(koWmfCapStylePen[capStyle]);
                    else
                        debugVectorImage << "WmfParser::createPenIndirect: invalid pen cap style" << style;

                    quint16 joinStyle = (style & PenJoinMask) >> 12;
                    if (joinStyle < 3)
                        handle->pen.setJoinStyle(koWmfJoinStylePen[joinStyle]);
                    else
                        debugVectorImage << "WmfParser::createPenIndirect: invalid pen join style" << style;

                    handle->pen.setColor(qtColor(color));
                    handle->pen.setWidth(width);
                    debugVectorImage << "Creating pen" << handle->pen;
                }
            } break;
            case (META_CREATEFONTINDIRECT & 0xff): {
                qint16 height; // Height of the character cell
                qint16 width; // Average width (not used)
                qint16 escapement; // The rotation of the text in 1/10th degrees
                qint16 orientation; // The rotation of each character
                QFont::Weight weight;
                quint16 property, fixedPitch, arg;

                KoWmfFontHandle *handle = new KoWmfFontHandle;

                if (addHandle(handle)) {
                    stream >> height >> width;
                    stream >> escapement >> orientation;
                    stream >> weight >> property >> arg >> arg;
                    stream >> fixedPitch;

                    // debugVectorImage << height << width << weight << property;
                    //  text rotation (in 1/10 degree)
                    handle->font.setFixedPitch(((fixedPitch & 0x01) == 0));
                    handle->escapement = escapement;
                    handle->orientation = orientation;

                    // A negative height means to use device units.
                    // debugVectorImage << "Font height:" << height;
                    handle->height = height;

                    // FIXME: For some reason this value needs to be multiplied by
                    //        a factor.  0.6 seems to give a good result, but why??
                    // ANSWER(?): The doc says the height is the height of the character cell.
                    //            But normally the font height is only the height above the
                    //            baseline, isn't it?
                    handle->font.setPointSize(qAbs(height) * 6 / 10);
                    if (weight == 0)
                        weight = QFont::Normal;
                    else {
                        // Linear transform between MS weights to Qt weights
                        // MS: 400=normal, 700=bold
                        // Qt: 50=normal, 75=bold
                        // This makes the line cross x=0 at y=50/3.  (x=MS weight, y=Qt weight)
                        //
                        // FIXME: Is this a linear relationship?
                        weight = static_cast<QFont::Weight>((50 + 3 * ((weight * (75 - 50)) / (700 - 400))) / 3);
                    }
                    handle->font.setWeight(weight);
                    handle->font.setItalic((property & 0x01));
                    handle->font.setUnderline((property & 0x100));
                    // TODO: Strikethrough

                    // font name
                    int maxChar = (size - 12) * 2;
                    char *nameFont = new char[maxChar];
                    stream.readRawData(nameFont, maxChar);
                    handle->font.setFamily(nameFont);
                    delete[] nameFont;
                }
            } break;
            case (META_CREATEBRUSHINDIRECT & 0xff): {
                Qt::BrushStyle style;
                quint16 sty, arg2;
                quint32 color;
                KoWmfBrushHandle *handle = new KoWmfBrushHandle;

                if (addHandle(handle)) {
                    stream >> sty >> color >> arg2;

                    if (sty == 2) {
                        if (arg2 < 6)
                            style = koWmfHatchedStyleBrush[arg2];
                        else {
                            debugVectorImage << "WmfParser::createBrushIndirect: invalid hatched brush" << arg2;
                            style = Qt::SolidPattern;
                        }
                    } else {
                        if (sty < 9)
                            style = koWmfStyleBrush[sty];
                        else {
                            debugVectorImage << "WmfParser::createBrushIndirect: invalid brush" << sty;
                            style = Qt::SolidPattern;
                        }
                    }
                    handle->brush.setStyle(style);
                    handle->brush.setColor(qtColor(color));
                }
            } break;
#if 0
 UNSPECIFIED in the Spec: 
    { &WmfParser::createBitmapIndirect, "createBitmapIndirect" }, //109 0xfd
    { &WmfParser::createBitmap, "createBitmap" }, // 110 0xfe
#endif
            case (META_CREATEREGION & 0xff):
                // FIXME: Unimplemented
                createEmptyObject();
                break;
            default:
                // function outside WMF specification
                errorVectorImage << "BROKEN WMF file: Record number" << Qt::hex << recordType << Qt::dec << " index " << index;
                mValid = false;
                break;
            }

            mBuffer->seek(bufferOffset + (size << 1));
        }

        // Let the backend clean up it's internal state.
        m_backend->end();
    }

    for (int i = 0; i < mNbrObject; i++) {
        if (mObjHandleTab[i] != nullptr)
            delete mObjHandleTab[i];
    }
    delete[] mObjHandleTab;
    mObjHandleTab = nullptr;

    return true;
}

//-----------------------------------------------------------------------------

void WmfParser::createBoundingBox(QDataStream &stream)
{
    // Check bounding rectangle for standard meta file.
    // This calculation is done in device coordinates.
    if (!mStandard || !mValid)
        return;

    bool windowExtIsSet = false;
    bool viewportExtIsSet = false;

    quint16 recordType = 1;
    quint32 size;

    int filePos;

    // Search for records setWindowOrg and setWindowExt to
    // determine what the total bounding box of this WMF is.
    // This initialization assumes that setWindowOrg comes before setWindowExt.
    qint16 windowOrgX = 0;
    qint16 windowOrgY = 0;
    qint16 windowWidth = 0;
    qint16 windowHeight = 0;
    qint16 viewportOrgX = 0;
    qint16 viewportOrgY = 0;
    qint16 viewportWidth = 0;
    qint16 viewportHeight = 0;
    bool bboxRecalculated = false;
    while (recordType) {
        filePos = mBuffer->pos();
        stream >> size >> recordType;

        if (size == 0) {
            debugVectorImage << "WmfParser: incorrect file!";
            mValid = 0;
            return;
        }

        bool doRecalculateBBox = false;
        qint16 orgX = 0;
        qint16 orgY = 0;
        qint16 extX = 0;
        qint16 extY = 0;
        switch (recordType &= 0xFF) {
        case 11: // setWindowOrg
        {
            stream >> windowOrgY >> windowOrgX;
#if DEBUG_BBOX
            debugVectorImage << "setWindowOrg" << windowOrgX << windowOrgY;
#endif
            if (!windowExtIsSet)
                break;

            // The bounding box doesn't change just because we get
            // a new window.  Remember we are working in device
            // (viewport) coordinates when deciding the bounding
            // box.
            if (viewportExtIsSet)
                break;

            // If there is no viewport, then use the window ext as
            // size, and (0, 0) as origin.
            //
            // FIXME: Handle the case where the window is defined
            //        first and then the viewport, without any
            //        drawing in between.  If that happens, I
            //        don't think that the window definition
            //        should influence the bounding box.
            orgX = 0;
            orgY = 0;
            extX = windowWidth;
            extY = windowHeight;
        } break;

        case 12: // setWindowExt
        {
            stream >> windowHeight >> windowWidth;
            windowExtIsSet = true;
            bboxRecalculated = false;

#if DEBUG_BBOX
            debugVectorImage << "setWindowExt" << windowWidth << windowHeight << "(viewportOrg = " << viewportOrgX << viewportOrgY << ")";
#endif

            // If the viewport is set, then a changed window
            // changes nothing in the bounding box.
            if (viewportExtIsSet)
                break;

            bboxRecalculated = false;

            // Collect the maximum width and height.
            if (abs(windowWidth - windowOrgX) > mMaxWidth)
                mMaxWidth = abs(windowWidth - windowOrgX);
            if (abs(windowHeight - windowOrgY) > mMaxHeight)
                mMaxHeight = abs(windowHeight - windowOrgY);

            orgX = 0;
            orgY = 0;
            extX = windowWidth;
            extY = windowHeight;
        } break;

        case 13: // setViewportOrg
        {
            stream >> viewportOrgY >> viewportOrgX;
            bboxRecalculated = false;

#if DEBUG_BBOX
            debugVectorImage << "setViewportOrg" << viewportOrgX << viewportOrgY;
#endif
            orgX = viewportOrgX;
            orgY = viewportOrgY;
            if (viewportExtIsSet) {
                extX = viewportWidth;
                extY = viewportHeight;
            } else {
                // If the viewportExt is not set, then either a
                // subsequent setViewportExt will set it, or the
                // windowExt will be used instead.
                extX = windowWidth;
                extY = windowHeight;
            }
            break;

            // FIXME: Handle the case where the org changes but
            //        there is no subsequent Ext change (should be
            //        rather uncommon).
        } break;

        case 14: // setViewportExt
        {
            stream >> viewportHeight >> viewportWidth;
            viewportExtIsSet = true;
            bboxRecalculated = false;

#if DEBUG_BBOX
            debugVectorImage << "setViewportExt" << viewportWidth << viewportHeight;
#endif
            orgX = viewportOrgX;
            orgY = viewportOrgY;
            extX = viewportWidth;
            extY = viewportHeight;
        } break;

            // FIXME: Also support:
            //          ScaleWindowExt, ScaleViewportExt,
            //          OffsetWindowOrg, OffsetViewportOrg

            // The following are drawing commands.  It is only when
            // there is an actual drawing command that we should check
            // the bounding box. It seems that some WMF files have
            // lots of changes of the window or viewports but without
            // any drawing commands in between. These changes should
            // not affect the bounding box.
        case 19: // lineTo
        // case 20: // moveTo
        case 23: // arc
        case 24: // ellipse
        case 26: // pie
        case 27: // rectangle
        case 28: // roundRect
        case 29: // patBlt
        case 31: // setPixel
        case 33: // textOut
        case 34: // bitBlt
        case 36: // polygon
        case 37: // polyline
        // case 38: // escape  FIXME: is this a drawing command?
        case 40: // fillRegion
        case 41:
        case 42:
        case 43:
        case 44:
        case 48: // chord
        case 50: // extTextOut
        case 56: // polyPolygon
        case 64: // dibBitBlt
        case 65: // dibStretchBlt
        case 67: // stretchDib
        case 72: // extFloodFill
#if DEBUG_BBOX
            debugVectorImage << "drawing record: " << (recordType & 0xff);
#endif
            doRecalculateBBox = true;
            break;

        default:;
        }

        // Recalculate the BBox if it was indicated above that it should be.
        if (doRecalculateBBox && !bboxRecalculated) {
#if DEBUG_BBOX
            debugVectorImage << "Recalculating BBox";
#endif
            // If we have a viewport, always use that one.
            if (viewportExtIsSet) {
                orgX = viewportOrgX;
                orgY = viewportOrgY;
                extX = viewportWidth;
                extY = viewportHeight;
            } else {
                // If there is no defined viewport, then use the
                // window as the fallback viewport. But only the size,
                // the origin is always (0, 0).
                orgX = 0;
                orgY = 0;
                extX = qAbs(windowWidth);
                extY = qAbs(windowHeight);
            }

            // If ext < 0, switch the org and org+ext
            if (extX < 0) {
                orgX += extX;
                extX = -extX;
            }
            if (extY < 0) {
                orgY += extY;
                extY = -extY;
            }

            // At this point, the ext is always >= 0, i.e. org <= org+ext
#if DEBUG_BBOX
            debugVectorImage << orgX << orgY << extX << extY;
#endif
            if (orgX < mBBoxLeft)
                mBBoxLeft = orgX;
            if (orgY < mBBoxTop)
                mBBoxTop = orgY;
            if (orgX + extX > mBBoxRight)
                mBBoxRight = orgX + extX;
            if (orgY + extY > mBBoxBottom)
                mBBoxBottom = orgY + extY;

            bboxRecalculated = true;
        }

#if DEBUG_BBOX
        if (isOrgOrExt) {
            debugVectorImage << "              mBBoxTop = " << mBBoxTop;
            debugVectorImage << "mBBoxLeft = " << mBBoxLeft << "  mBBoxRight = " << mBBoxRight;
            debugVectorImage << "           MBBoxBotton = " << mBBoxBottom;
            debugVectorImage << "Max width,height = " << mMaxWidth << mMaxHeight;
        }
#endif

        mBuffer->seek(filePos + (size << 1));
    }
}

//-----------------------------------------------------------------------------
// Object handle

void WmfParser::createEmptyObject()
{
    // allocation of an empty object (to keep object counting in sync)
    KoWmfPenHandle *handle = new KoWmfPenHandle;

    addHandle(handle);
}

//-----------------------------------------------------------------------------
// Misc functions

quint16 WmfParser::calcCheckSum(WmfPlaceableHeader *apmfh)
{
    quint16 *lpWord;
    quint16 wResult, i;

    // Start with the first word
    wResult = *(lpWord = (quint16 *)(apmfh));
    // XOR in each of the other 9 words
    for (i = 1; i <= 9; i++) {
        wResult ^= lpWord[i];
    }
    return wResult;
}

//-----------------------------------------------------------------------------
// Utilities and conversion Wmf -> Qt

bool WmfParser::addHandle(KoWmfHandle *handle)
{
    int idx;

    for (idx = 0; idx < mNbrObject; idx++) {
        if (mObjHandleTab[idx] == nullptr)
            break;
    }

    if (idx < mNbrObject) {
        mObjHandleTab[idx] = handle;
        return true;
    } else {
        delete handle;
        mStackOverflow = true;
        debugVectorImage << "WmfParser::addHandle : stack overflow = broken file !";
        return false;
    }
}

void WmfParser::deleteHandle(int idx)
{
    if ((idx < mNbrObject) && (mObjHandleTab[idx] != nullptr)) {
        delete mObjHandleTab[idx];
        mObjHandleTab[idx] = nullptr;
    } else {
        debugVectorImage << "WmfParser::deletehandle() : bad index number";
    }
}

void WmfParser::pointArray(QDataStream &stream, QPolygon &pa)
{
    qint16 left, top;
    int i, max;

    for (i = 0, max = pa.size(); i < max; i++) {
        stream >> left >> top;
        pa.setPoint(i, left, top);
    }
}

void WmfParser::xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int &angleStart, int &angleLength)
{
    double aStart, aLength;

    aStart = atan2((double)yStart, (double)xStart);
    aLength = atan2((double)yEnd, (double)xEnd) - aStart;

    angleStart = (int)((aStart * 2880) / 3.14166);
    angleLength = (int)((aLength * 2880) / 3.14166);
    if (angleLength < 0)
        angleLength = 5760 + angleLength;
}

QPainter::CompositionMode WmfParser::winToQtComposition(quint16 param) const
{
    if (param < 17)
        return koWmfOpTab16[param];
    else
        return QPainter::CompositionMode_Source;
}

QPainter::CompositionMode WmfParser::winToQtComposition(quint32 param) const
{
    /* TODO: Ternary raster operations
    0x00C000CA  dest = (source AND pattern)
    0x00F00021  dest = pattern
    0x00FB0A09  dest = DPSnoo
    0x005A0049  dest = pattern XOR dest   */
    int i;

    for (i = 0; i < 15; i++) {
        if (koWmfOpTab32[i].winRasterOp == param)
            break;
    }

    if (i < 15)
        return koWmfOpTab32[i].qtRasterOp;
    else
        return QPainter::CompositionMode_SourceOver;
}

typedef struct {
    char bmType[2];
    qint32 bmSize;
    qint16 bmReserved1;
    qint16 bmReserved2;
    qint32 bmOffBits;
} BMPFILEHEADER;
static const int BMPFILEHEADER_SIZE = 14;

static QDataStream &operator<<(QDataStream &s, const BMPFILEHEADER &header)
{
    s.writeRawData(header.bmType, 2);
    s << header.bmSize << header.bmReserved1 << header.bmReserved2 << header.bmOffBits;
    return s;
}

bool WmfParser::dibToBmp(QImage &bmp, QDataStream &stream, quint32 size)
{
    int sizeBmp = size + BMPFILEHEADER_SIZE;
    QByteArray pattern; // BMP header and DIB data
    pattern.resize(sizeBmp);
    QDataStream outputStream(&pattern, QIODevice::WriteOnly);
    outputStream.setByteOrder(QDataStream::LittleEndian);

    // Build a BMP header
    BMPFILEHEADER bmpHeader;
    bmpHeader.bmType[0] = 'B';
    bmpHeader.bmType[1] = 'M';
    bmpHeader.bmSize = sizeBmp;
    bmpHeader.bmOffBits = 0;

    outputStream << bmpHeader;
    stream.readRawData(pattern.data() + BMPFILEHEADER_SIZE, size);

    if (!bmp.loadFromData(pattern, "BMP")) {
        debugVectorImage << "WmfParser::dibToBmp: invalid bitmap";
        return false;
    } else {
        return true;
    }
}
}
