/* Windows Meta File Loader/Painter Class Implementation
 *
 * SPDX-FileCopyrightText: 1998 Stefan Taferner
 * SPDX-FileCopyrightText: 2002 thierry lorthiois
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

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

#include <cassert>
#include <cmath>

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

#include "ktnef_debug.h"

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

#define QWMF_DEBUG 0
using namespace Qt::Literals::StringLiterals;

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

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

class WinObjHandle
{
public:
    virtual ~WinObjHandle() = default;

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

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

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

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

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

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;
    int filePos;
    int idx;
    int i;
    WmfCmd *cmd;
    WmfCmd *last;
    DWORD rdSize;
    WORD rdFunc;

    mTextAlign = 0;
    mRotation = 0;
    mTextColor = Qt::black;
    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;
        const WORD 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;
    int 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;
            QString param;
            if (metaFuncTab[idx].name == nullptr) {
                str += "UNKNOWN "_L1;
            }
            if (metaFuncTab[idx].method == &QWinMetaFile::noop) {
                str += "UNIMPLEMENTED "_L1;
            }
            str += QLatin1StringView(metaFuncTab[idx].name);
            str += " : "_L1;

            for (i = 0; i < cmd->numParm; ++i) {
                param.setNum(cmd->parm[i]);
                str += param;
                str += u' ';
            }
            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;
    int j;
    int 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;
    int 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;
    int yCenter;
    int angleStart;
    int 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;
    int yCenter;
    int angleStart;
    int 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;
    int yCenter;
    int angleStart;
    int 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)
{
    auto 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;
    int y;
    int width;
    int 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(QLatin1StringView(text)).width() + fm.descent(); // because fm.width(text) isn't right 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, QLatin1StringView(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, QLatin1StringView(text.mid(i, 1)));
        }
    } else {
        mPainter.drawText(x, y, width, height, Qt::AlignLeft | Qt::AlignTop, QLatin1StringView(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)
{
    auto 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)
    auto 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;
    auto 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;
    auto 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)
{
    auto handle = new WinObjFontHandle;
    addHandle(handle);

    QString family(QLatin1StringView((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(static_cast<QFont::Weight>((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;
    WORD 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;
    int green;
    int blue;

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

    return {red, green, blue};
}

//-----------------------------------------------------------------------------
void QWinMetaFile::xyToAngle(int xStart, int yStart, int xEnd, int yEnd, int &angleStart, int &angleLength)
{
    float aStart;
    float 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)
{
    using BMPFILEHEADER = struct _BMPFILEHEADER {
        WORD bmType;
        DWORD bmSize;
        WORD bmReserved1;
        WORD bmReserved2;
        DWORD bmOffBits;
    };

    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;
    }
}
