/* Windows Meta File Loader/Painter Class Implementation
 *
 * Copyright ( C ) 1998 Stefan Taferner
 * Modified 2002 thierry lorthiois
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or ( at your
 * option ) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABLILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details. You should have received a copy
 * of the GNU General Public License along with this program; if not, write
 * to the Free Software Foundation, Inc, 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include <config-ktnef.h> // WORDS_BIGENDIAN

#include <math.h>
#include <assert.h>

#include <QImage>
#include <QPainter>
#include <QPainterPath>
#include <QDataStream>
#include <QBuffer>
#include <QFile>
#include <QPolygon>

#include "ktnef_debug.h"

bool qwmfDebug = false;

#include "qwmf.h"
#include "wmfstruct.h"
#include "metafuncs.h"

#define QWMF_DEBUG  0

class WmfCmd
{
public:
    ~WmfCmd()
    {
        delete next;
    }

    WmfCmd *next;
    unsigned short funcIndex;
    long numParm;
    short *parm;
};

class WinObjHandle
{
public:
    virtual ~WinObjHandle()
    {
    }

    virtual void apply(QPainter &p) = 0;
};

class WinObjBrushHandle : public WinObjHandle
{
public:
    void apply(QPainter &p) override;
    QBrush brush;
    virtual ~WinObjBrushHandle()
    {
    }
};

class WinObjPenHandle : public WinObjHandle
{
public:
    void apply(QPainter &p) override;
    QPen pen;
    virtual ~WinObjPenHandle()
    {
    }
};

class WinObjPatternBrushHandle : public WinObjHandle
{
public:
    void apply(QPainter &p) override;
    QBrush brush;
    QImage image;
    virtual ~WinObjPatternBrushHandle()
    {
    }
};

class WinObjFontHandle : public WinObjHandle
{
public:
    void apply(QPainter &p) override;
    QFont font;
    int rotation;
    virtual ~WinObjFontHandle()
    {
    }
};

void WinObjBrushHandle::apply(QPainter &p)
{
    p.setBrush(brush);
}

void WinObjPenHandle::apply(QPainter &p)
{
    p.setPen(pen);
}

void WinObjPatternBrushHandle::apply(QPainter &p)
{
    p.setBrush(brush);
}

void WinObjFontHandle::apply(QPainter &p)
{
    p.setFont(font);
}

#define MAX_OBJHANDLE 64

//-----------------------------------------------------------------------------
QWinMetaFile::QWinMetaFile()
{
    mValid = false;
    mFirstCmd = nullptr;
    mObjHandleTab = nullptr;
    mDpi = 1000;
}

//-----------------------------------------------------------------------------
QWinMetaFile::~QWinMetaFile()
{
    delete mFirstCmd;
    if (mObjHandleTab) {
        delete[] mObjHandleTab;
    }
}

//-----------------------------------------------------------------------------
bool QWinMetaFile::load(const QString &filename)
{
    QFile file(filename);

    if (!file.exists()) {
        qCDebug(KTNEFAPPS_LOG) << "File" << QFile::encodeName(filename) << " does not exist";
        return false;
    }

    if (!file.open(QIODevice::ReadOnly)) {
        qCDebug(KTNEFAPPS_LOG) << "Cannot open file" << QFile::encodeName(filename);
        return false;
    }

    QByteArray ba = file.readAll();
    file.close();

    QBuffer buffer(&ba);
    buffer.open(QIODevice::ReadOnly);
    return load(buffer);
}

//-----------------------------------------------------------------------------
bool QWinMetaFile::load(QBuffer &buffer)
{
    QDataStream st;
    WmfEnhMetaHeader eheader;
    WmfMetaHeader header;
    WmfPlaceableHeader pheader;
    WORD checksum;
    int filePos, idx, i;
    WmfCmd *cmd, *last;
    DWORD rdSize;
    WORD rdFunc;

    mTextAlign = 0;
    mRotation = 0;
    mTextColor = Qt::black;
    if (mFirstCmd) {
        delete mFirstCmd;
    }
    mFirstCmd = nullptr;

    st.setDevice(&buffer);
    st.setByteOrder(QDataStream::LittleEndian);   // Great, I love Qt !

    //----- Read placeable metafile header
    st >> pheader.key;
    mIsPlaceable = (pheader.key == (DWORD)APMHEADER_KEY);
    if (mIsPlaceable) {
        st >> pheader.hmf;
        st >> pheader.bbox.left;
        st >> pheader.bbox.top;
        st >> pheader.bbox.right;
        st >> pheader.bbox.bottom;
        st >> pheader.inch;
        st >> pheader.reserved;
        st >> pheader.checksum;
        checksum = calcCheckSum(&pheader);
        if (pheader.checksum != checksum) {
            mIsPlaceable = false;
        }

        mDpi = pheader.inch;
        mBBox.setLeft(pheader.bbox.left);
        mBBox.setTop(pheader.bbox.top);
        mBBox.setRight(pheader.bbox.right);
        mBBox.setBottom(pheader.bbox.bottom);
        mHeaderBoundingBox = mBBox;
        if (QWMF_DEBUG) {
            qCDebug(KTNEFAPPS_LOG) << "\n-------------------------------------------------";
            qCDebug(KTNEFAPPS_LOG) << "WMF Placeable Header (" << static_cast<int>(sizeof(pheader)) << "):";
            qCDebug(KTNEFAPPS_LOG) << "  bbox=(" << mBBox.left() << ";" << mBBox.top() << ";" << mBBox.width()
                                   << "; " << mBBox.height() << ")";
            qCDebug(KTNEFAPPS_LOG) << "  inch=" << pheader.inch;
            qCDebug(KTNEFAPPS_LOG) << "  checksum=" << pheader.checksum << "("
                                   << (pheader.checksum == checksum ? "ok" : "wrong") << " )";
        }
    } else {
        buffer.reset();
    }

    //----- Read as enhanced metafile header
    filePos = buffer.pos();
    st >> eheader.iType;
    st >> eheader.nSize;
    st >> eheader.rclBounds.left;
    st >> eheader.rclBounds.top;
    st >> eheader.rclBounds.right;
    st >> eheader.rclBounds.bottom;
    st >> eheader.rclFrame.left;
    st >> eheader.rclFrame.top;
    st >> eheader.rclFrame.right;
    st >> eheader.rclFrame.bottom;
    st >> eheader.dSignature;
    mIsEnhanced = (eheader.dSignature == ENHMETA_SIGNATURE);
    if (mIsEnhanced) { // is it really enhanced ?
        st >> eheader.nVersion;
        st >> eheader.nBytes;
        st >> eheader.nRecords;
        st >> eheader.nHandles;
        st >> eheader.sReserved;
        st >> eheader.nDescription;
        st >> eheader.offDescription;
        st >> eheader.nPalEntries;
        st >> eheader.szlDevice.width;
        st >> eheader.szlDevice.height;
        st >> eheader.szlMillimeters.width;
        st >> eheader.szlMillimeters.height;

        if (QWMF_DEBUG) {
            qCDebug(KTNEFAPPS_LOG) << "\n-------------------------------------------------";
            qCDebug(KTNEFAPPS_LOG) << "WMF Extended Header:";
            qCDebug(KTNEFAPPS_LOG) << "  iType=" << eheader.iType;
            qCDebug(KTNEFAPPS_LOG) << "  nSize=" << eheader.nSize;
            qCDebug(KTNEFAPPS_LOG) << "  rclBounds=(" << eheader.rclBounds.left << ";" << eheader.rclBounds.top << ";"
                                   << eheader.rclBounds.right << "; " << eheader.rclBounds.bottom << ")";
            qCDebug(KTNEFAPPS_LOG) << "  rclFrame=(" << eheader.rclFrame.left << ";" << eheader.rclFrame.top << ";"
                                   << eheader.rclFrame.right << "; " << eheader.rclFrame.bottom << ")";
            qCDebug(KTNEFAPPS_LOG) << "  nBytes=" << eheader.nBytes;
            qCDebug(KTNEFAPPS_LOG) << "\nNOT YET IMPLEMENTED, SORRY.";
        }
    } else { // no, not enhanced
        //----- Read as standard metafile header
        buffer.seek(filePos);
        st >> header.mtType;
        st >> header.mtHeaderSize;
        st >> header.mtVersion;
        st >> header.mtSize;
        st >> header.mtNoObjects;
        st >> header.mtMaxRecord;
        st >> header.mtNoParameters;
        if (QWMF_DEBUG) {
            qCDebug(KTNEFAPPS_LOG) << "WMF Header:" << "mtSize=" << header.mtSize;
        }
    }

    //----- Test header validity
    mValid = ((header.mtHeaderSize == 9) && (header.mtNoParameters == 0)) || mIsEnhanced || mIsPlaceable;
    if (mValid) {
        //----- Read Metafile Records
        last = nullptr;
        rdFunc = -1;
        while (!st.atEnd() && (rdFunc != 0)) {
            st >> rdSize;
            st >> rdFunc;
            idx = findFunc(rdFunc);
            rdSize -= 3;

            cmd = new WmfCmd;
            cmd->next = nullptr;
            if (last) {
                last->next = cmd;
            } else {
                mFirstCmd = cmd;
            }

            cmd->funcIndex = idx;
            cmd->numParm = rdSize;
            cmd->parm = new WORD[ rdSize ];
            last = cmd;

            for (i = 0; i < rdSize && !st.atEnd(); ++i) {
                st >> cmd->parm[ i ];
            }

            if (rdFunc == 0x020B) {           // SETWINDOWORG: dimensions
                mBBox.setLeft(cmd->parm[ 1 ]);
                mBBox.setTop(cmd->parm[ 0 ]);
            }
            if (rdFunc == 0x020C) {           // SETWINDOWEXT: dimensions
                mBBox.setWidth(cmd->parm[ 1 ]);
                mBBox.setHeight(cmd->parm[ 0 ]);
            }

            if (i < rdSize) {
                qCDebug(KTNEFAPPS_LOG) << "WMF : file truncated !";
                return false;
            }
        }
        //----- Test records validities
        mValid = (rdFunc == 0) && (mBBox.width() != 0) && (mBBox.height() != 0);
        if (!mValid) {
            qCDebug(KTNEFAPPS_LOG) << "WMF : incorrect file format !";
        }
    } else {
        qCDebug(KTNEFAPPS_LOG) << "WMF Header : incorrect header !";
    }

    buffer.close();
    return mValid;
}

//-----------------------------------------------------------------------------
bool QWinMetaFile::paint(QPaintDevice *aTarget, bool absolute)
{
    int idx, i;
    WmfCmd *cmd;

    if (!mValid) {
        return false;
    }

    assert(aTarget != nullptr);
    if (mPainter.isActive()) {
        return false;
    }

    if (mObjHandleTab) {
        delete[] mObjHandleTab;
    }
    mObjHandleTab = new WinObjHandle * [ MAX_OBJHANDLE ];
    for (i = MAX_OBJHANDLE - 1; i >= 0; --i) {
        mObjHandleTab[ i ] = nullptr;
    }

    mPainter.resetTransform();
    mWinding = false;
    mAbsoluteCoord = absolute;

    mPainter.begin(aTarget);
    if (QWMF_DEBUG) {
        qCDebug(KTNEFAPPS_LOG) << "Bounding box :" << mBBox.left()
                               << " " << mBBox.top() << " " << mBBox.right() << " " << mBBox.bottom();
    }

    if (mAbsoluteCoord) {
        mPainter.setWindow(mBBox.top(), mBBox.left(), mBBox.width(), mBBox.height());
    }
    mInternalWorldMatrix.reset();

    for (cmd = mFirstCmd; cmd; cmd = cmd->next) {
        idx = cmd->funcIndex;
        (this->*metaFuncTab[ idx ].method)(cmd->numParm, cmd->parm);

        if (QWMF_DEBUG) {
            QString str, param;
            if (metaFuncTab[ idx ].name == nullptr) {
                str += QLatin1String("UNKNOWN ");
            }
            if (metaFuncTab[ idx ].method == &QWinMetaFile::noop) {
                str += QLatin1String("UNIMPLEMENTED ");
            }
            str += QLatin1String(metaFuncTab[ idx ].name);
            str += QLatin1String(" : ");

            for (i = 0; i < cmd->numParm; ++i) {
                param.setNum(cmd->parm[ i ]);
                str += param;
                str += QLatin1Char(' ');
            }
            qCDebug(KTNEFAPPS_LOG) << str;
        }
    }
    /*
        // TODO: cleanup this code when QPicture::setBoundingBox() is possible in KOClipart (QT31)
        // because actually QPicture::boundingBox() != mBBox()
        mWindowsCoord += 1;
        if ( mWindowsCoord == 2 )  {
            qCDebug(KTNEFAPPS_LOG) <<"DRAW ANGLES";
            mPainter.setPen( Qt::white );
            mPainter.drawPoint( mBBox.left(), mBBox.top()  );
            mPainter.drawPoint( mBBox.right(), mBBox.bottom() );
        }
    */
    mPainter.end();
    return true;
}

//----------------s-------------------------------------------------------------
// Metafile painter methods
//-----------------------------------------------------------------------------
void QWinMetaFile::setWindowOrg(long, short *parm)
{
    if (mAbsoluteCoord) {
        QRect r = mPainter.window();
        mPainter.setWindow(parm[ 1 ], parm[ 0 ], r.width(), r.height());
    } else {
        double dx = mInternalWorldMatrix.dx();
        double dy = mInternalWorldMatrix.dy();

        mInternalWorldMatrix.translate(-dx, -dy);
        mInternalWorldMatrix.translate(-parm[ 1 ], -parm[ 0 ]);
        mPainter.translate(-dx, -dy);
        mPainter.translate(-parm[ 1 ], -parm[ 0 ]);
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::setWindowExt(long, short *parm)
{
    // negative value allowed for width and height : QABS() forbidden
    if (mAbsoluteCoord) {
        QRect r = mPainter.window();
        mPainter.setWindow(r.left(), r.top(), parm[ 1 ], parm[ 0 ]);
    } else {
        if ((parm[ 0 ] != 0) && (parm[ 1 ] != 0)) {
            QRect r = mPainter.window();
            double dx = mInternalWorldMatrix.dx();
            double dy = mInternalWorldMatrix.dy();
            double sx = mInternalWorldMatrix.m11();
            double sy = mInternalWorldMatrix.m22();

            mInternalWorldMatrix.translate(-dx, -dy);
            mInternalWorldMatrix.scale(1 / sx, 1 / sy);
            mPainter.translate(-dx, -dy);
            mPainter.scale(1 / sx, 1 / sy);

            sx = (double)r.width() / (double)parm[ 1 ];
            sy = (double)r.height() / (double)parm[ 0 ];

            mInternalWorldMatrix.scale(sx, sy);
            mInternalWorldMatrix.translate(dx, dy);
            mPainter.scale(sx, sy);
            mPainter.translate(dx, dy);
        }
    }
}

//-----------------------------------------------------------------------------
// Drawing
//-----------------------------------------------------------------------------
void QWinMetaFile::lineTo(long, short *parm)
{
    mPainter.drawLine(mLastPos, QPoint(parm[1], parm[0]));
}

//-----------------------------------------------------------------------------
void QWinMetaFile::moveTo(long, short *parm)
{
    mLastPos = QPoint(parm[ 1 ], parm[ 0 ]);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::ellipse(long, short *parm)
{
    mPainter.drawEllipse(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::polygon(long, short *parm)
{
    QPolygon *pa;              // causing a memleck ???

    pa = pointArray(parm[ 0 ], &parm[ 1 ]);
    if (mWinding) {
        mPainter.drawPolygon(*pa, Qt::WindingFill);
    } else {
        mPainter.drawPolygon(*pa, Qt::OddEvenFill);
    }

    delete pa;
}

//-----------------------------------------------------------------------------
void QWinMetaFile::polyPolygon(long, short *parm)
{
    QRegion region;
    int i, j, startPolygon;

    mPainter.save();

    // define clipping region
    QRect win = bbox();
    startPolygon = 1 + parm[ 0 ];
    for (i = 0; i < parm[ 0 ]; ++i) {
        QPolygon pa1(parm[ 1 + i ]);
        for (j = 0; j < parm[ 1 + i ]; ++j) {
            pa1.setPoint(j, parm[ startPolygon ], parm[ startPolygon + 1 ]);
            startPolygon += 2;
        }
        QRegion r(pa1);
        region = region.xored(r);
    }
    mPainter.setClipRegion(region);

    // fill polygons
    mPainter.fillRect(win.left(), win.top(), win.width(), win.height(), mPainter.brush());

    // draw polygon's border if necessary
    if (mPainter.pen().style() != Qt::NoPen) {
        mPainter.setClipping(false);
        mPainter.setBrush(Qt::NoBrush);

        QPolygon *pa;
        int idxPolygon = 1 + parm[ 0 ];
        for (i = 0; i < parm[ 0 ]; ++i) {
            pa = pointArray(parm[ 1 + i ], &parm[ idxPolygon ]);
            mPainter.drawPolygon(*pa);
            idxPolygon += parm[ 1 + i ] * 2;
        }
    }

    mPainter.restore();
}

//-----------------------------------------------------------------------------
void QWinMetaFile::polyline(long, short *parm)
{
    QPolygon *pa;

    pa = pointArray(parm[ 0 ], &parm[ 1 ]);
    mPainter.drawPolyline(*pa);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::rectangle(long, short *parm)
{
    mPainter.drawRect(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::roundRect(long, short *parm)
{
    int xRnd = 0, yRnd = 0;

    // convert (xRound, yRound) in percentage
    if ((parm[ 3 ] - parm[ 5 ]) != 0) {
        xRnd = (parm[ 1 ] * 100) / (parm[ 3 ] - parm[ 5 ]);
    }
    if ((parm[ 2 ] - parm[ 4 ]) != 0) {
        yRnd = (parm[ 0 ] * 100) / (parm[ 2 ] - parm[ 4 ]);
    }

    mPainter.drawRoundedRect(parm[ 5 ], parm[ 4 ], parm[ 3 ] - parm[ 5 ], parm[ 2 ] - parm[ 4 ],
                             xRnd, yRnd, Qt::RelativeSize);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::arc(long, short *parm)
{
    int xCenter, yCenter, angleStart, aLength;

    xCenter = parm[ 7 ] + ((parm[ 5 ] - parm[ 7 ]) / 2);
    yCenter = parm[ 6 ] + ((parm[ 4 ] - parm[ 6 ]) / 2);

    xyToAngle(parm[ 3 ] - xCenter, yCenter - parm[ 2 ], parm[ 1 ] - xCenter, yCenter - parm[ 0 ], angleStart, aLength);

    mPainter.drawArc(parm[ 7 ], parm[ 6 ], parm[ 5 ] - parm[ 7 ], parm[ 4 ] - parm[ 6 ], angleStart, aLength);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::chord(long, short *parm)
{
    int xCenter, yCenter, angleStart, aLength;

    xCenter = parm[ 7 ] + ((parm[ 5 ] - parm[ 7 ]) / 2);
    yCenter = parm[ 6 ] + ((parm[ 4 ] - parm[ 6 ]) / 2);

    xyToAngle(parm[ 3 ] - xCenter, yCenter - parm[ 2 ], parm[ 1 ] - xCenter, yCenter - parm[ 0 ], angleStart, aLength);

    mPainter.drawChord(parm[ 7 ], parm[ 6 ], parm[ 5 ] - parm[ 7 ], parm[ 4 ] - parm[ 6 ], angleStart, aLength);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::pie(long, short *parm)
{
    int xCenter, yCenter, angleStart, aLength;

    xCenter = parm[ 7 ] + ((parm[ 5 ] - parm[ 7 ]) / 2);
    yCenter = parm[ 6 ] + ((parm[ 4 ] - parm[ 6 ]) / 2);

    xyToAngle(parm[ 3 ] - xCenter, yCenter - parm[ 2 ], parm[ 1 ] - xCenter, yCenter - parm[ 0 ], angleStart, aLength);

    mPainter.drawPie(parm[ 7 ], parm[ 6 ], parm[ 5 ] - parm[ 7 ], parm[ 4 ] - parm[ 6 ], angleStart, aLength);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::setPolyFillMode(long, short *parm)
{
    mWinding = parm[ 0 ];
}

//-----------------------------------------------------------------------------
void QWinMetaFile::setBkColor(long, short *parm)
{
    mPainter.setBackground(QBrush(color(parm)));
}

//-----------------------------------------------------------------------------
void QWinMetaFile::setBkMode(long, short *parm)
{
    if (parm[ 0 ] == 1) {
        mPainter.setBackgroundMode(Qt::TransparentMode);
    } else {
        mPainter.setBackgroundMode(Qt::OpaqueMode);
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::setPixel(long, short *parm)
{
    QPen pen = mPainter.pen();
    mPainter.setPen(color(parm));
    mPainter.drawPoint(parm[ 3 ], parm[ 2 ]);
    mPainter.setPen(pen);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::setRop(long, short *parm)
{
    mPainter.setCompositionMode(winToQtComposition(parm[ 0 ]));
}

//-----------------------------------------------------------------------------
void QWinMetaFile::saveDC(long, short *)
{
    mPainter.save();
}

//-----------------------------------------------------------------------------
void QWinMetaFile::restoreDC(long, short *parm)
{
    for (int i = 0; i > parm[ 0 ]; i--) {
        mPainter.restore();
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::intersectClipRect(long, short *parm)
{
    /*  TODO: better implementation : need QT 3.0.2
        QRegion region = mPainter.clipRegion();
        if ( region.isEmpty() )
            region = bbox();
    */
    QRegion region(bbox());

    QRegion newRegion(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]);
    region = region.intersected(newRegion);

    mPainter.setClipRegion(region);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::excludeClipRect(long, short *parm)
{
    /*  TODO: better implementation : need QT 3.0.2
        QRegion region = mPainter.clipRegion();
        if ( region.isEmpty() )
            region = bbox();
    */
    QRegion region(bbox());

    QRegion newRegion(parm[ 3 ], parm[ 2 ], parm[ 1 ] - parm[ 3 ], parm[ 0 ] - parm[ 2 ]);
    region = region.subtracted(newRegion);

    mPainter.setClipRegion(region);
}

//-----------------------------------------------------------------------------
// Text
//-----------------------------------------------------------------------------
void QWinMetaFile::setTextColor(long, short *parm)
{
    mTextColor = color(parm);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::setTextAlign(long, short *parm)
{
    mTextAlign = parm[ 0 ];
}

//-----------------------------------------------------------------------------
void QWinMetaFile::textOut(long num, short *parm)
{
    short *copyParm = new short[ num + 1 ];

    // re-order parameters
    int idxOffset = (parm[ 0 ] / 2) + 1 + (parm[ 0 ] & 1);
    copyParm[ 0 ] = parm[ idxOffset ];
    copyParm[ 1 ] = parm[ idxOffset + 1 ];
    copyParm[ 2 ] = parm[ 0 ];
    copyParm[ 3 ] = 0;
    memcpy(&copyParm[ 4 ], &parm[ 1 ], parm[ 0 ]);

    extTextOut(num + 1, copyParm);
    delete [] copyParm;
}

//-----------------------------------------------------------------------------
void QWinMetaFile::extTextOut(long num, short *parm)
{
    char *ptStr;
    int x, y, width, height;
    int idxOffset;

    if (parm[ 3 ] != 0) {       // ETO_CLIPPED flag add 4 parameters
        ptStr = (char *)&parm[ 8 ];
    } else {
        ptStr = (char *)&parm[ 4 ];
    }

    QByteArray text(ptStr, parm[ 2 ] + 1);

    QFontMetrics fm(mPainter.font());
    width = fm.boundingRect(QLatin1String(text)).width() + fm.descent();    // because fm.width(text) isn't rigth with Italic text
    height = fm.height();

    mPainter.save();

    if (mTextAlign & 0x01) {         // (left, top) position = current logical position
        x = mLastPos.x();
        y = mLastPos.y();
    } else {                          // (left, top) position = parameters
        x = parm[ 1 ];
        y = parm[ 0 ];
    }

    if (mRotation) {
        mPainter.translate(parm[ 1 ], parm[ 0 ]);
        mPainter.rotate(mRotation);
        mPainter.translate(-parm[ 1 ], -parm[ 0 ]);
    }

    // alignment
    if (mTextAlign & 0x06) {
        x -= (width / 2);
    }
    if (mTextAlign & 0x08) {
        y -= (height - fm.descent());
    }

    mPainter.setPen(mTextColor);
    idxOffset = (parm[ 2 ] / 2) + 4 + (parm[ 2 ] & 1);
    if ((parm[ 2 ] > 1) && (num >= (idxOffset + parm[ 2 ])) && (parm[ 3 ] == 0)) {
        // offset for each char
        int left = x;
        mPainter.drawText(left, y, width, height, Qt::AlignLeft | Qt::AlignTop, QLatin1String(text.mid(0, 1)));
        for (int i = 1; i < parm[ 2 ]; ++i) {
            left += parm[ idxOffset + i - 1 ];
            mPainter.drawText(left, y, width, height, Qt::AlignLeft | Qt::AlignTop, QLatin1String(text.mid(i, 1)));
        }
    } else {
        mPainter.drawText(x, y, width, height, Qt::AlignLeft | Qt::AlignTop, QLatin1String(text));
    }

    mPainter.restore();
}

//-----------------------------------------------------------------------------
// Bitmap
//-----------------------------------------------------------------------------
void QWinMetaFile::dibBitBlt(long num, short *parm)
{
    if (num > 9) {        // DIB image
        QImage bmpSrc;

        if (dibToBmp(bmpSrc, (char *)&parm[ 8 ], (num - 8) * 2)) {
            long raster = toDWord(parm);

            mPainter.setCompositionMode(winToQtComposition(raster));

            // wmf file allow negative width or height
            mPainter.save();
            if (parm[ 5 ] < 0) {    // width < 0 => horizontal flip
                QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
                mPainter.setWorldTransform(m, true);
            }
            if (parm[ 4 ] < 0) {    // height < 0 => vertical flip
                QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
                mPainter.setWorldTransform(m, true);
            }
            mPainter.drawImage(parm[ 7 ], parm[ 6 ], bmpSrc, parm[ 3 ], parm[ 2 ], parm[ 5 ], parm[ 4 ]);
            mPainter.restore();
        }
    } else {
        qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::dibBitBlt without image: not implemented";
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::dibStretchBlt(long num, short *parm)
{
    QImage bmpSrc;

    if (dibToBmp(bmpSrc, (char *)&parm[ 10 ], (num - 10) * 2)) {
        long raster = toDWord(parm);

        mPainter.setCompositionMode(winToQtComposition(raster));

        // wmf file allow negative width or height
        mPainter.save();
        if (parm[ 7 ] < 0) {    // width < 0 => horizontal flip
            QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
            mPainter.setWorldTransform(m, true);
        }
        if (parm[ 6 ] < 0) {    // height < 0 => vertical flip
            QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
            mPainter.setWorldTransform(m, true);
        }
        bmpSrc = bmpSrc.copy(parm[ 5 ], parm[ 4 ], parm[ 3 ], parm[ 2 ]);
        // TODO: scale the bitmap ( QImage::scale(parm[ 7 ], parm[ 6 ]) is actually too slow )

        mPainter.drawImage(parm[ 9 ], parm[ 8 ], bmpSrc);
        mPainter.restore();
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::stretchDib(long num, short *parm)
{
    QImage bmpSrc;

    if (dibToBmp(bmpSrc, (char *)&parm[ 11 ], (num - 11) * 2)) {
        long raster = toDWord(parm);

        mPainter.setCompositionMode(winToQtComposition(raster));

        // wmf file allow negative width or height
        mPainter.save();
        if (parm[ 8 ] < 0) {    // width < 0 => horizontal flip
            QTransform m(-1.0F, 0.0F, 0.0F, 1.0F, 0.0F, 0.0F);
            mPainter.setWorldTransform(m, true);
        }
        if (parm[ 7 ] < 0) {    // height < 0 => vertical flip
            QTransform m(1.0F, 0.0F, 0.0F, -1.0F, 0.0F, 0.0F);
            mPainter.setWorldTransform(m, true);
        }
        bmpSrc = bmpSrc.copy(parm[ 6 ], parm[ 5 ], parm[ 4 ], parm[ 3 ]);
        // TODO: scale the bitmap ( QImage::scale(parm[ 8 ], parm[ 7 ]) is actually too slow )

        mPainter.drawImage(parm[ 10 ], parm[ 9 ], bmpSrc);
        mPainter.restore();
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::dibCreatePatternBrush(long num, short *parm)
{
    WinObjPatternBrushHandle *handle = new WinObjPatternBrushHandle;
    addHandle(handle);
    QImage bmpSrc;

    if (dibToBmp(bmpSrc, (char *)&parm[ 2 ], (num - 2) * 2)) {
        handle->image = bmpSrc;
        handle->brush.setTextureImage(handle->image);
    }
}

//-----------------------------------------------------------------------------
// Object handle
//-----------------------------------------------------------------------------
void QWinMetaFile::selectObject(long, short *parm)
{
    int idx = parm[ 0 ];
    if (idx >= 0 && idx < MAX_OBJHANDLE && mObjHandleTab[ idx ]) {
        mObjHandleTab[ idx ]->apply(mPainter);
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::deleteObject(long, short *parm)
{
    deleteHandle(parm[ 0 ]);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::createEmptyObject(long, short *)
{
    // allocation of an empty object (to keep object counting in sync)
    WinObjPenHandle *handle = new WinObjPenHandle;
    addHandle(handle);
    qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile: unimplemented createObject";
}

//-----------------------------------------------------------------------------
void QWinMetaFile::createBrushIndirect(long, short *parm)
{
    static Qt::BrushStyle hatchedStyleTab[] = {
        Qt::HorPattern,
        Qt::FDiagPattern,
        Qt::BDiagPattern,
        Qt::CrossPattern,
        Qt::DiagCrossPattern
    };
    static Qt::BrushStyle styleTab[] = {
        Qt::SolidPattern,
        Qt::NoBrush,
        Qt::FDiagPattern,                                    /* hatched */
        Qt::Dense4Pattern,                                   /* should be custom bitmap pattern */
        Qt::HorPattern,                                      /* should be BS_INDEXED (?) */
        Qt::VerPattern,                                      /* should be device-independent bitmap */
        Qt::Dense6Pattern,                                   /* should be device-independent packed-bitmap */
        Qt::Dense2Pattern,                                   /* should be BS_PATTERN8x8 */
        Qt::Dense3Pattern                                    /* should be device-independent BS_DIBPATTERN8x8 */
    };
    Qt::BrushStyle style;
    short arg;
    WinObjBrushHandle *handle = new WinObjBrushHandle;
    addHandle(handle);

    arg = parm[ 0 ];
    if (arg == 2) {
        arg = parm[ 3 ];
        if (arg >= 0 && arg < 5) {
            style = hatchedStyleTab[ arg ];
        } else {
            qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::createBrushIndirect: invalid hatched brush" << arg;
            style = Qt::SolidPattern;
        }
    } else if (arg >= 0 && arg < 9) {
        style = styleTab[ arg ];
    } else {
        qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::createBrushIndirect: invalid brush" << arg;
        style = Qt::SolidPattern;
    }
    handle->brush.setStyle(style);
    handle->brush.setColor(color(parm + 1));
}

//-----------------------------------------------------------------------------
void QWinMetaFile::createPenIndirect(long, short *parm)
{
    static Qt::PenStyle styleTab[] = {
        Qt::SolidLine, Qt::DashLine, Qt::DotLine, Qt::DashDotLine, Qt::DashDotDotLine,
        Qt::NoPen, Qt::SolidLine
    };
    Qt::PenStyle style;
    WinObjPenHandle *handle = new WinObjPenHandle;
    addHandle(handle);

    if (parm[ 0 ] >= 0 && parm[ 0 ] < 6) {
        style = styleTab[ parm[ 0 ] ];
    } else {
        qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::createPenIndirect: invalid pen" << parm[ 0 ];
        style = Qt::SolidLine;
    }

    handle->pen.setStyle(style);
    handle->pen.setColor(color(parm + 3));
    handle->pen.setCapStyle(Qt::RoundCap);

    //int width = 0;
    // TODO : width of pen proportional to device context width
    // DOESN'T WORK
    /*
        QRect devRec;
        devRec = mPainter.transformed( mBBox );
        width = ( parm[ 0 ] * devRec.width() ) / mBBox.width() ;
        qCDebug(KTNEFAPPS_LOG) <<"CreatePenIndirect:";
        qCDebug(KTNEFAPPS_LOG) <<"   log coord. :" << mBBox.width() <<"" << mBBox.height();
        qCDebug(KTNEFAPPS_LOG) <<"   log. pen :" << parm[ 1 ] <<"" << parm[ 2 ];
        qCDebug(KTNEFAPPS_LOG) <<"   dev. pen :" << width;
        handle->pen.setWidth( width );
    */
}

//-----------------------------------------------------------------------------
void QWinMetaFile::createFontIndirect(long, short *parm)
{
    WinObjFontHandle *handle = new WinObjFontHandle;
    addHandle(handle);

    QString family(QLatin1String((const char *)&parm[ 9 ]));

    mRotation = -parm[ 2 ]  / 10;               // text rotation (in 1/10 degree)
    // TODO: memorisation of rotation in object Font
    handle->font.setFamily(family);
    handle->font.setFixedPitch(((parm[ 8 ] & 0x01) == 0));
    // TODO: investigation why some test case need -2. (size of font in logical point)
    handle->font.setPointSize(qAbs(parm[ 0 ]) - 2);
    handle->font.setWeight((parm[ 4 ] >> 3));
    handle->font.setItalic((parm[ 5 ] & 0x01));
    handle->font.setUnderline((parm[ 5 ] & 0x100));
}

//-----------------------------------------------------------------------------
// Misc
//-----------------------------------------------------------------------------
void QWinMetaFile::noop(long, short *)
{
}

void QWinMetaFile::end(long, short *)
{
    // end of file :
//    qCDebug(KTNEFAPPS_LOG) <<"END bbox=(" << mBBox.left() <<";" << mBBox.top() <<";" << mBBox.width() <<";" << mBBox.height() <<")";
}

//-----------------------------------------------------------------------------
unsigned short QWinMetaFile::calcCheckSum(WmfPlaceableHeader *apmfh)
{
    WORD *lpWord;
    WORD wResult, i;

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

//-----------------------------------------------------------------------------
int QWinMetaFile::findFunc(unsigned short aFunc) const
{
    int i;

    for (i = 0; metaFuncTab[ i ].name; ++i) {
        if (metaFuncTab[ i ].func == aFunc) {
            return i;
        }
    }

    // here : unknown function
    return i;
}

//-----------------------------------------------------------------------------
QPolygon *QWinMetaFile::pointArray(short num, short *parm)
{
    int i;

    mPoints.resize(num);

    for (i = 0; i < num; ++i, parm += 2) {
        mPoints.setPoint(i, parm[ 0 ], parm[ 1 ]);
    }

    return &mPoints;
}

//-----------------------------------------------------------------------------
unsigned int QWinMetaFile::toDWord(short *parm)
{
    unsigned int l;

#if !defined(WORDS_BIGENDIAN)
    l = *(unsigned int *)(parm);
#else
    char *bytes;
    char swap[ 4 ];
    bytes = (char *)parm;
    swap[ 0 ] = bytes[ 2 ];
    swap[ 1 ] = bytes[ 3 ];
    swap[ 2 ] = bytes[ 0 ];
    swap[ 3 ] = bytes[ 1 ];
    l = *(unsigned int *)(swap);
#endif

    return l;
}

//-----------------------------------------------------------------------------
QColor QWinMetaFile::color(short *parm)
{
    unsigned int colorRef;
    int red, green, blue;

    colorRef = toDWord(parm) & 0xffffff;
    red = colorRef & 255;
    green = (colorRef >> 8) & 255;
    blue = (colorRef >> 16) & 255;

    return QColor(red, green, blue);
}

//-----------------------------------------------------------------------------
void QWinMetaFile::xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int &angleStart, int &angleLength)
{
    float 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;
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::addHandle(WinObjHandle *handle)
{
    int idx;

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

    if (idx < MAX_OBJHANDLE) {
        mObjHandleTab[ idx ] = handle;
    } else {
        qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile error: handle table full !";
    }
}

//-----------------------------------------------------------------------------
void QWinMetaFile::deleteHandle(int idx)
{
    if (idx >= 0 && idx < MAX_OBJHANDLE && mObjHandleTab[ idx ]) {
        delete mObjHandleTab[ idx ];
        mObjHandleTab[ idx ] = nullptr;
    }
}

//-----------------------------------------------------------------------------
QPainter::CompositionMode QWinMetaFile::winToQtComposition(short parm) const
{
    static const QPainter::CompositionMode opTab[] = {
        // ### untested (conversion from Qt::RasterOp)
        QPainter::CompositionMode_Source, // Qt::CopyROP
        QPainter::CompositionMode_Clear, // Qt::ClearROP
        QPainter::CompositionMode_SourceOut, // Qt::NandROP
        QPainter::CompositionMode_SourceOut, // Qt::NotAndROP
        QPainter::CompositionMode_DestinationOut, // Qt::NotCopyROP
        QPainter::CompositionMode_DestinationOut, // Qt::AndNotROP
        QPainter::CompositionMode_DestinationOut, // Qt::NotROP
        QPainter::CompositionMode_Xor, // Qt::XorROP
        QPainter::CompositionMode_Source, // Qt::NorROP
        QPainter::CompositionMode_SourceIn, // Qt::AndROP
        QPainter::CompositionMode_SourceIn, // Qt::NotXorROP
        QPainter::CompositionMode_Destination, // Qt::NopROP
        QPainter::CompositionMode_Destination, // Qt::NotOrROP
        QPainter::CompositionMode_Source, // Qt::CopyROP
        QPainter::CompositionMode_Source, // Qt::OrNotROP
        QPainter::CompositionMode_SourceOver, // Qt::OrROP
        QPainter::CompositionMode_Source // Qt::SetROP
    };

    if (parm > 0 && parm <= 16) {
        return opTab[ parm ];
    } else {
        return QPainter::CompositionMode_Source;
    }
}

//-----------------------------------------------------------------------------
QPainter::CompositionMode QWinMetaFile::winToQtComposition(long parm) const
{
    /* TODO: Ternary raster operations
    0x00C000CA  dest = (source AND pattern)
    0x00F00021  dest = pattern
    0x00FB0A09  dest = DPSnoo
    0x005A0049  dest = pattern XOR dest   */
    static const struct OpTab {
        long winRasterOp;
        QPainter::CompositionMode qtRasterOp;
    } opTab[] = {
        // ### untested (conversion from Qt::RasterOp)
        { 0x00CC0020, QPainter::CompositionMode_Source }, // CopyROP
        { 0x00EE0086, QPainter::CompositionMode_SourceOver }, // OrROP
        { 0x008800C6, QPainter::CompositionMode_SourceIn }, // AndROP
        { 0x00660046, QPainter::CompositionMode_Xor }, // XorROP
        { 0x00440328, QPainter::CompositionMode_DestinationOut }, // AndNotROP
        { 0x00330008, QPainter::CompositionMode_DestinationOut }, // NotCopyROP
        { 0x001100A6, QPainter::CompositionMode_SourceOut }, // NandROP
        { 0x00C000CA, QPainter::CompositionMode_Source }, // CopyROP
        { 0x00BB0226, QPainter::CompositionMode_Destination }, // NotOrROP
        { 0x00F00021, QPainter::CompositionMode_Source }, // CopyROP
        { 0x00FB0A09, QPainter::CompositionMode_Source }, // CopyROP
        { 0x005A0049, QPainter::CompositionMode_Source }, // CopyROP
        { 0x00550009, QPainter::CompositionMode_DestinationOut }, // NotROP
        { 0x00000042, QPainter::CompositionMode_Clear }, // ClearROP
        { 0x00FF0062, QPainter::CompositionMode_Source } // SetROP
    };

    int i;
    for (i = 0; i < 15; ++i) {
        if (opTab[ i ].winRasterOp == parm) {
            break;
        }
    }

    if (i < 15) {
        return opTab[ i ].qtRasterOp;
    } else {
        return QPainter::CompositionMode_Source;
    }
}

//-----------------------------------------------------------------------------
bool QWinMetaFile::dibToBmp(QImage &bmp, const char *dib, long size)
{
    typedef struct _BMPFILEHEADER {
        WORD bmType;
        DWORD bmSize;
        WORD bmReserved1;
        WORD bmReserved2;
        DWORD bmOffBits;
    }  BMPFILEHEADER;

    int sizeBmp = size + 14;
    QByteArray pattern;       // BMP header and DIB data
    pattern.fill(0, sizeBmp);    //resize and fill
    pattern.insert(14, QByteArray::fromRawData(dib, size));

    // add BMP header
    BMPFILEHEADER *bmpHeader;
    bmpHeader = (BMPFILEHEADER *)(pattern.constData());
    bmpHeader->bmType = 0x4D42;
    bmpHeader->bmSize = sizeBmp;

    if (!bmp.loadFromData((const uchar *)bmpHeader, pattern.size(), "BMP")) {
        qCDebug(KTNEFAPPS_LOG) << "QWinMetaFile::dibToBmp: invalid bitmap";
        return false;
    } else {
//        if ( bmp.save("/home/software/kde-cvs/qt/examples/wmf/test.bmp", "BMP") )
//        if ( bmp.load( "/home/software/kde-cvs/qt/examples/wmf/test.bmp", "BMP" ) )
//            fprintf(stderr, "Bitmap ok \n");
        return true;
    }
}
