/*
  SPDX-FileCopyrightText: 2008 Brad Hards <bradh@frogmouth.net>
  SPDX-FileCopyrightText: 2009-2010 Inge Wallin <inge@lysator.liu.se>

  SPDX-License-Identifier: LGPL-2.1-or-later
*/

#include "EmfOutputPainterStrategy.h"

#include <math.h>

#include <QPainterPath>

#include <VectorImageDebug.h>

#include "EmfObjects.h"

#define DEBUG_EMFPAINT 0
#define DEBUG_PAINTER_TRANSFORM 0

namespace Libemf
{

static QPainter::CompositionMode rasteropToQtComposition(long rop);

// ================================================================
//                         Class OutputPainterStrategy

OutputPainterStrategy::OutputPainterStrategy()
    : m_header(nullptr)
    , m_path(nullptr)
    , m_currentlyBuildingPath(false)
    , m_fillRule(Qt::OddEvenFill)
    , m_mapMode(MM_TEXT)
    , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT
    , m_currentCoords()
{
    m_painter = nullptr;
    m_painterSaves = 0;
    m_outputSize = QSize();
    m_keepAspectRatio = true;
}

OutputPainterStrategy::OutputPainterStrategy(QPainter &painter, QSize &size, bool keepAspectRatio)
    : m_header(nullptr)
    , m_path(nullptr)
    , m_currentlyBuildingPath(false)
    , m_windowExtIsSet(false)
    , m_viewportExtIsSet(false)
    , m_windowViewportIsSet(false)
    , m_fillRule(Qt::OddEvenFill)
    , m_mapMode(MM_TEXT)
    , m_textAlignMode(TA_NOUPDATECP) // == TA_TOP == TA_LEFT
    , m_currentCoords()
{
    m_painter = &painter;
    m_painterSaves = 0;
    m_outputSize = size;
    m_keepAspectRatio = keepAspectRatio;
}

OutputPainterStrategy::~OutputPainterStrategy()
{
    delete m_header;
    delete m_path;
}

void OutputPainterStrategy::paintBounds(const Header *header)
{
    // The rectangle is in device coordinates.
    QRectF rect(header->bounds());
    m_painter->save();

    // Draw a simple cross in a rectangle to show the bounds.
    m_painter->setPen(QPen(QColor(172, 196, 206), 0));
    m_painter->drawRect(rect);
    m_painter->drawLine(rect.topLeft(), rect.bottomRight());
    m_painter->drawLine(rect.bottomLeft(), rect.topRight());

    m_painter->restore();
}

void OutputPainterStrategy::init(const Header *header)
{
    // Save the header since we need the frame and bounds inside the drawing.
    m_header = new Header(*header);

    QSize headerBoundsSize = header->bounds().size();

#if DEBUG_EMFPAINT
    debugVectorImage << "----------------------------------------------------------------------";
    debugVectorImage << "Shape size               =" << m_outputSize.width() << m_outputSize.height() << " pt";
    debugVectorImage << "----------------------------------------------------------------------";
    debugVectorImage << "Boundary box (dev units) =" << header->bounds().x() << header->bounds().y() << header->bounds().width() << header->bounds().height();
    debugVectorImage << "Frame (phys size)        =" << header->frame().x() << header->frame().y() << header->frame().width() << header->frame().height()
                     << " *0.01 mm";

    debugVectorImage << "Device =" << header->device().width() << header->device().height();
    debugVectorImage << "Millimeters =" << header->millimeters().width() << header->millimeters().height();
#endif

#if DEBUG_PAINTER_TRANSFORM
    printPainterTransform("In init, before save:");
#endif

    // This is restored in cleanup().
    m_painter->save();

    // Calculate how much the painter should be resized to fill the
    // outputSize with output.
    qreal scaleX = qreal(m_outputSize.width()) / headerBoundsSize.width();
    qreal scaleY = qreal(m_outputSize.height()) / headerBoundsSize.height();
    if (m_keepAspectRatio) {
        // Use the smaller value so that we don't get an overflow in
        // any direction.
        if (scaleX > scaleY)
            scaleX = scaleY;
        else
            scaleY = scaleX;
    }
#if DEBUG_EMFPAINT
    debugVectorImage << "scale = " << scaleX << ", " << scaleY;
#endif

    // Transform the EMF object so that it fits in the shape.  The
    // topleft of the EMF will be the top left of the shape.
    m_painter->scale(scaleX, scaleY);
    m_painter->translate(-header->bounds().left(), -header->bounds().top());
#if DEBUG_PAINTER_TRANSFORM
    printPainterTransform("after fitting into shape");
#endif

    // Save the scale so that we can use it when setting lineWidth.
    m_outputScale = (scaleX + scaleY) / 2;

    // Calculate translation if we should center the EMF in the
    // area and keep the aspect ratio.
#if 0 // Should apparently be upper left.  See bug 265868
    if ( m_keepAspectRatio ) {
        m_painter->translate((m_outputSize.width() / scaleX - headerBoundsSize.width()) / 2,
                             (m_outputSize.height() / scaleY - headerBoundsSize.height()) / 2);
#if DEBUG_PAINTER_TRANSFORM
        printPainterTransform("after translation for keeping center in the shape");
#endif
    }
#endif

    m_outputTransform = m_painter->transform();
    m_worldTransform = QTransform();

    // For calculations of window / viewport during the painting
    m_windowOrg = QPoint(0, 0);
    m_viewportOrg = QPoint(0, 0);
    m_windowExtIsSet = false;
    m_viewportExtIsSet = false;
    m_windowViewportIsSet = false;

#if DEBUG_EMFPAINT
    paintBounds(header);
#endif
}

void OutputPainterStrategy::cleanup(const Header *header)
{
    Q_UNUSED(header);

#if DEBUG_EMFPAINT
    if (m_painterSaves > 0)
        debugVectorImage << "WARNING: UNRESTORED DC's:" << m_painterSaves;
#endif

    // Restore all the save()s that were done during the processing.
    for (int i = 0; i < m_painterSaves; ++i)
        m_painter->restore();
    m_painterSaves = 0;

    // Restore the painter to what it was before init() was called.
    m_painter->restore();
}

void OutputPainterStrategy::eof()
{
}

void OutputPainterStrategy::setPixelV(QPoint &point, quint8 red, quint8 green, quint8 blue, quint8 reserved)
{
    Q_UNUSED(reserved);

#if DEBUG_EMFPAINT
    debugVectorImage << point << red << green << blue;
#endif

    m_painter->save();

    QPen pen;
    pen.setColor(QColor(red, green, blue));
    m_painter->setPen(pen);
    m_painter->drawPoint(point);

    m_painter->restore();
}

void OutputPainterStrategy::beginPath()
{
#if DEBUG_EMFPAINT
    debugVectorImage;
#endif

    delete (m_path);
    m_path = new QPainterPath;
    m_currentlyBuildingPath = true;
}

void OutputPainterStrategy::closeFigure()
{
#if DEBUG_EMFPAINT
    debugVectorImage;
#endif

    m_path->closeSubpath();
}

void OutputPainterStrategy::endPath()
{
#if DEBUG_EMFPAINT
    debugVectorImage;
#endif

    m_path->setFillRule(m_fillRule);
    m_currentlyBuildingPath = false;
}

void OutputPainterStrategy::saveDC()
{
#if DEBUG_EMFPAINT
    debugVectorImage;
#endif

    // A little trick here: Save the worldTransform in the painter.
    // If we didn't do this, we would have to create a separate stack
    // for these.
    //
    // FIXME: We should collect all the parts of the DC that are not
    //        stored in the painter and save them separately.
    QTransform savedTransform = m_painter->worldTransform();
    m_painter->setWorldTransform(m_worldTransform);

    m_painter->save();
    ++m_painterSaves;

    m_painter->setWorldTransform(savedTransform);
}

void OutputPainterStrategy::restoreDC(const qint32 savedDC)
{
#if DEBUG_EMFPAINT
    debugVectorImage << savedDC;
#endif

    // Note that savedDC is always negative
    for (int i = 0; i < -savedDC; ++i) {
        if (m_painterSaves > 0) {
            m_painter->restore();
            --m_painterSaves;
        } else {
            debugVectorImage << "restoreDC(): try to restore painter without save" << savedDC - i;
            break;
        }
    }

    // We used a trick in saveDC() and stored the worldTransform in
    // the painter.  Now restore the full transformation.
    m_worldTransform = m_painter->worldTransform();
    QTransform newMatrix = m_worldTransform * m_outputTransform;
    m_painter->setWorldTransform(newMatrix);
}

void OutputPainterStrategy::setMetaRgn()
{
    debugVectorImage << "EMR_SETMETARGN not yet implemented";
}

// ----------------------------------------------------------------
//                 World Transform, Window and Viewport

// General note about coordinate spaces and transforms:
//
// There are several coordinate spaces in use when drawing an EMF file:
//  1. The object space, in which the objects' coordinates are expressed inside the EMF.
//     In general there are several of these.
//  2. The page space, which is where they end up being painted in the EMF picture.
//     The union of these form the bounding box of the EMF.
//  3. (possibly) the output space, where the EMF picture itself is placed
//     and/or scaled, rotated, etc
//
// The transform between spaces 1. and 2. is called the World Transform.
// The world transform can be changed either through calls to change
// the window or viewport or through calls to setWorldTransform() or
// modifyWorldTransform().
//
// The transform between spaces 2. and 3. is the transform that the QPainter
// already contains when it is given to us.  We need to save this and reapply
// it after the world transform has changed. We call this transform the Output
// Transform in lack of a better word. (Some sources call it the Device Transform.)
//

// An unanswered question:
//
// The file mp07_embedded_ppt.pptx in the test files contains the
// following sequence of records:
// - SetWorldTransform
// - ModifyWorldTransform
// - SetViewportOrg  <-- doesn't change anything
// - SetWindowOrg    <-- doesn't change anything
// - ExtTextOutw
//
// I was previously under the impression that whenever a
// Set{Window,Viewport}{Org,Ext} record was encountered, the world
// transform was supposed to be recalculated. But in this file, it
// destroys the world transform. The question is which of the
// following alternatives is true:
//
// 1. The world transform should only be recalculated if the
//    Set{Window,Viewport}{Org,Ext} record actually changes anything.
//
// 2. The transformations set by {Set,Modify}WorldTransform records
//    should always be reapplied after a change in window or viewport.
//
// 3. Something else
//
// I have for now implemented option 1. See the FIXME's in
// SetWindowOrg et al.
//

// Set Window and Viewport
void OutputPainterStrategy::recalculateWorldTransform()
{
    m_worldTransform = QTransform();

    // If neither the window nor viewport extension is set, then there
    // is no way to perform the calculation.  Just give up.
    if (!m_windowExtIsSet && !m_viewportExtIsSet)
        return;

    // Negative window extensions mean flip the picture.  Handle this here.
    bool flip = false;
    qreal midpointX = 0.0;
    qreal midpointY = 0.0;
    qreal scaleX = 1.0;
    qreal scaleY = 1.0;
    if (m_windowExt.width() < 0) {
        midpointX = m_windowOrg.x() + m_windowExt.width() / qreal(2.0);
        scaleX = -1.0;
        flip = true;
    }
    if (m_windowExt.height() < 0) {
        midpointY = m_windowOrg.y() + m_windowExt.height() / qreal(2.0);
        scaleY = -1.0;
        flip = true;
    }
    if (flip) {
        // debugVectorImage << "Flipping" << midpointX << midpointY << scaleX << scaleY;
        m_worldTransform.translate(midpointX, midpointY);
        m_worldTransform.scale(scaleX, scaleY);
        m_worldTransform.translate(-midpointX, -midpointY);
        // debugVectorImage << "After flipping for window" << mWorldTransform;
    }

    // Update the world transform if both window and viewport are set...
    // FIXME: Check windowExt == 0 in any direction
    if (m_windowExtIsSet && m_viewportExtIsSet) {
        // Both window and viewport are set.
        qreal windowViewportScaleX = qreal(m_viewportExt.width()) / qreal(m_windowExt.width());
        qreal windowViewportScaleY = qreal(m_viewportExt.height()) / qreal(m_windowExt.height());

        m_worldTransform.translate(-m_windowOrg.x(), -m_windowOrg.y());
        m_worldTransform.scale(windowViewportScaleX, windowViewportScaleY);
        m_worldTransform.translate(m_viewportOrg.x(), m_viewportOrg.y());
    }

    // ...and apply it to the painter
    m_painter->setWorldTransform(m_worldTransform);
    m_windowViewportIsSet = true;

    // Apply the output transform.
    QTransform newMatrix = m_worldTransform * m_outputTransform;
    m_painter->setWorldTransform(newMatrix);
}

void OutputPainterStrategy::setWindowOrgEx(const QPoint &origin)
{
#if DEBUG_EMFPAINT
    debugVectorImage << origin;
#endif

    // FIXME: See unanswered question at the start of this section.
    if (m_windowOrg == origin) {
        // debugVectorImage << "same origin as before";
        return;
    }

    m_windowOrg = origin;

    recalculateWorldTransform();
}

void OutputPainterStrategy::setWindowExtEx(const QSize &size)
{
#if DEBUG_EMFPAINT
    debugVectorImage << size;
#endif

    // FIXME: See unanswered question at the start of this section.
    if (m_windowExt == size) {
        // debugVectorImage << "same extension as before";
        return;
    }

    m_windowExt = size;
    m_windowExtIsSet = true;

    recalculateWorldTransform();
}

void OutputPainterStrategy::setViewportOrgEx(const QPoint &origin)
{
#if DEBUG_EMFPAINT
    debugVectorImage << origin;
#endif

    // FIXME: See unanswered question at the start of this section.
    if (m_viewportOrg == origin) {
        // debugVectorImage << "same origin as before";
        return;
    }

    m_viewportOrg = origin;

    recalculateWorldTransform();
}

void OutputPainterStrategy::setViewportExtEx(const QSize &size)
{
#if DEBUG_EMFPAINT
    debugVectorImage << size;
#endif

    // FIXME: See unanswered question at the start of this section.
    if (m_viewportExt == size) {
        // debugVectorImage << "same extension as before";
        return;
    }

    m_viewportExt = size;
    m_viewportExtIsSet = true;

    recalculateWorldTransform();
}

void OutputPainterStrategy::modifyWorldTransform(quint32 mode, float M11, float M12, float M21, float M22, float Dx, float Dy)
{
#if DEBUG_EMFPAINT
    if (mode == MWT_IDENTITY)
        debugVectorImage << "Identity matrix";
    else
        debugVectorImage << mode << M11 << M12 << M21 << M22 << Dx << Dy;
#endif

    QTransform matrix(M11, M12, M21, M22, Dx, Dy);

    if (mode == MWT_IDENTITY) {
        m_worldTransform = QTransform();
    } else if (mode == MWT_LEFTMULTIPLY) {
        m_worldTransform = matrix * m_worldTransform;
    } else if (mode == MWT_RIGHTMULTIPLY) {
        m_worldTransform = m_worldTransform * matrix;
    } else if (mode == MWT_SET) {
        m_worldTransform = matrix;
    } else {
        warnVectorImage << "Unimplemented transform mode" << mode;
    }

    // Apply the output transform.
    QTransform newMatrix = m_worldTransform * m_outputTransform;
    m_painter->setWorldTransform(newMatrix);
}

void OutputPainterStrategy::setWorldTransform(float M11, float M12, float M21, float M22, float Dx, float Dy)
{
#if DEBUG_EMFPAINT
    debugVectorImage << M11 << M12 << M21 << M22 << Dx << Dy;
#endif

    QTransform matrix(M11, M12, M21, M22, Dx, Dy);

    m_worldTransform = matrix;

    // Apply the output transform.
    QTransform newMatrix = m_worldTransform * m_outputTransform;
    m_painter->setWorldTransform(newMatrix);
}

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

void OutputPainterStrategy::createPen(quint32 ihPen, quint32 penStyle, quint32 x, quint32 y, quint8 red, quint8 green, quint8 blue, quint8 reserved)
{
    Q_UNUSED(y);
    Q_UNUSED(reserved);

#if DEBUG_EMFPAINT
    debugVectorImage << ihPen << hex << penStyle << dec << x << y << red << green << blue << reserved;
#endif

    QPen pen;
    pen.setColor(QColor(red, green, blue));

    if (penStyle & PS_GEOMETRIC) {
        pen.setCosmetic(false);
    } else {
        pen.setCosmetic(true);
    }

    switch (penStyle & 0xF) {
    case PS_SOLID:
        pen.setStyle(Qt::SolidLine);
        break;
    case PS_DASH:
        pen.setStyle(Qt::DashLine);
        break;
    case PS_DOT:
        pen.setStyle(Qt::DotLine);
        break;
    case PS_DASHDOT:
        pen.setStyle(Qt::DashDotLine);
        break;
    case PS_DASHDOTDOT:
        pen.setStyle(Qt::DashDotDotLine);
        break;
    case PS_NULL:
        pen.setStyle(Qt::NoPen);
        break;
    case PS_INSIDEFRAME:
        // FIXME: We don't properly support this
        pen.setStyle(Qt::SolidLine);
        break;
    case PS_USERSTYLE:
        debugVectorImage << "UserStyle pen not yet supported, using SolidLine";
        pen.setStyle(Qt::SolidLine);
        break;
    case PS_ALTERNATE:
        debugVectorImage << "Alternate pen not yet supported, using DashLine";
        pen.setStyle(Qt::DashLine);
        break;
    default:
        debugVectorImage << "unexpected pen type, using SolidLine" << (penStyle & 0xF);
        pen.setStyle(Qt::SolidLine);
    }

    switch (penStyle & PS_ENDCAP_FLAT) {
    case PS_ENDCAP_ROUND:
        pen.setCapStyle(Qt::RoundCap);
        break;
    case PS_ENDCAP_SQUARE:
        pen.setCapStyle(Qt::SquareCap);
        break;
    case PS_ENDCAP_FLAT:
        pen.setCapStyle(Qt::FlatCap);
        break;
    default:
        debugVectorImage << "unexpected cap style, using SquareCap" << (penStyle & PS_ENDCAP_FLAT);
        pen.setCapStyle(Qt::SquareCap);
    }
    pen.setWidthF(x * m_outputScale);

    m_objectTable.insert(ihPen, pen);
}

void OutputPainterStrategy::createBrushIndirect(quint32 ihBrush, quint32 brushStyle, quint8 red, quint8 green, quint8 blue, quint8 reserved, quint32 brushHatch)
{
    Q_UNUSED(reserved);
    Q_UNUSED(brushHatch);

#if DEBUG_EMFPAINT
    debugVectorImage << ihBrush << hex << brushStyle << dec << red << green << blue << reserved << brushHatch;
#endif

    QBrush brush;

    switch (brushStyle) {
    case BS_SOLID:
        brush.setStyle(Qt::SolidPattern);
        break;
    case BS_NULL:
        brush.setStyle(Qt::NoBrush);
        break;
    case BS_HATCHED:
        brush.setStyle(Qt::CrossPattern);
        break;
    case BS_PATTERN:
        Q_ASSERT(0);
        break;
    case BS_INDEXED:
        Q_ASSERT(0);
        break;
    case BS_DIBPATTERN:
        Q_ASSERT(0);
        break;
    case BS_DIBPATTERNPT:
        Q_ASSERT(0);
        break;
    case BS_PATTERN8X8:
        Q_ASSERT(0);
        break;
    case BS_DIBPATTERN8X8:
        Q_ASSERT(0);
        break;
    case BS_MONOPATTERN:
        Q_ASSERT(0);
        break;
    default:
        Q_ASSERT(0);
    }

    brush.setColor(QColor(red, green, blue));

    // TODO: Handle the BrushHatch enum.

    m_objectTable.insert(ihBrush, brush);
}

void OutputPainterStrategy::createMonoBrush(quint32 ihBrush, Bitmap *bitmap)
{
    QImage pattern(bitmap->image());
    QBrush brush(pattern);

    m_objectTable.insert(ihBrush, brush);
}

void OutputPainterStrategy::extCreateFontIndirectW(const ExtCreateFontIndirectWRecord &extCreateFontIndirectW)
{
    QFont font(extCreateFontIndirectW.fontFace());

    font.setWeight(convertFontWeight(extCreateFontIndirectW.weight()));

    if (extCreateFontIndirectW.height() < 0) {
        font.setPixelSize(-1 * extCreateFontIndirectW.height());
    } else if (extCreateFontIndirectW.height() > 0) {
        font.setPixelSize(extCreateFontIndirectW.height());
    } // zero is "use a default size" which is effectively no-op here.

    // .snp files don't always provide 0x01 for italics
    if (extCreateFontIndirectW.italic() != 0x00) {
        font.setItalic(true);
    }

    if (extCreateFontIndirectW.underline() != 0x00) {
        font.setUnderline(true);
    }

    m_objectTable.insert(extCreateFontIndirectW.ihFonts(), font);
}

void OutputPainterStrategy::selectStockObject(const quint32 ihObject)
{
#if DEBUG_EMFPAINT
    debugVectorImage << ihObject;
#endif

    switch (ihObject) {
    case WHITE_BRUSH:
        m_painter->setBrush(QBrush(Qt::white));
        break;
    case LTGRAY_BRUSH:
        m_painter->setBrush(QBrush(Qt::lightGray));
        break;
    case GRAY_BRUSH:
        m_painter->setBrush(QBrush(Qt::gray));
        break;
    case DKGRAY_BRUSH:
        m_painter->setBrush(QBrush(Qt::darkGray));
        break;
    case BLACK_BRUSH:
        m_painter->setBrush(QBrush(Qt::black));
        break;
    case NULL_BRUSH:
        m_painter->setBrush(QBrush());
        break;
    case WHITE_PEN:
        m_painter->setPen(QPen(Qt::white));
        break;
    case BLACK_PEN:
        m_painter->setPen(QPen(Qt::black));
        break;
    case NULL_PEN:
        m_painter->setPen(QPen(Qt::NoPen));
        break;
    case OEM_FIXED_FONT:
    case ANSI_FIXED_FONT:
    case SYSTEM_FIXED_FONT: {
        QFont font(QString("Fixed"));
        m_painter->setFont(font);
        break;
    }
    case ANSI_VAR_FONT:
    case DEFAULT_GUI_FONT: // Not sure if this is true, but it should work well
    {
        QFont font(QString("Helvetica")); // Could also be "System"
        m_painter->setFont(font);
        break;
    } break;
    case SYSTEM_FONT:
        // TODO: handle this
        break;
    case DEVICE_DEFAULT_FONT:
        // TODO: handle this
        break;
    case DEFAULT_PALETTE:
        break;
    case DC_BRUSH:
        // FIXME
        break;
    case DC_PEN:
        // FIXME
        break;
    default:
        warnVectorImage << "Unexpected stock object:" << (ihObject & 0x8000000);
    }
}

void OutputPainterStrategy::selectObject(const quint32 ihObject)
{
#if DEBUG_EMFPAINT
    debugVectorImage << hex << ihObject << dec;
#endif

    if (ihObject & 0x80000000) {
        selectStockObject(ihObject);
    } else {
        QVariant obj = m_objectTable.value(ihObject);

        switch (obj.type()) {
        case QVariant::Pen:
            m_painter->setPen(obj.value<QPen>());
            break;
        case QVariant::Brush:
            m_painter->setBrush(obj.value<QBrush>());
            break;
        case QVariant::Font:
            m_painter->setFont(obj.value<QFont>());
            break;
        default:
            debugVectorImage << "Unexpected type:" << obj.typeName();
        }
    }
}

void OutputPainterStrategy::deleteObject(const quint32 ihObject)
{
    m_objectTable.take(ihObject);
}

void OutputPainterStrategy::arc(const QRect &box, const QPoint &start, const QPoint &end)
{
#if DEBUG_EMFPAINT
    debugVectorImage << box << start << end;
#endif

    QPoint centrePoint = box.center();

    qreal startAngle = angleFromArc(centrePoint, start);
    qreal endAngle = angleFromArc(centrePoint, end);
    qreal spanAngle = angularSpan(startAngle, endAngle);

    m_painter->drawArc(box, startAngle * 16, spanAngle * 16);
}

void OutputPainterStrategy::chord(const QRect &box, const QPoint &start, const QPoint &end)
{
#if DEBUG_EMFPAINT
    debugVectorImage << box << start << end;
#endif

    QPoint centrePoint = box.center();

    qreal startAngle = angleFromArc(centrePoint, start);
    qreal endAngle = angleFromArc(centrePoint, end);
    qreal spanAngle = angularSpan(startAngle, endAngle);

    m_painter->drawChord(box, startAngle * 16, spanAngle * 16);
}

void OutputPainterStrategy::pie(const QRect &box, const QPoint &start, const QPoint &end)
{
#if DEBUG_EMFPAINT
    debugVectorImage << box << start << end;
#endif

    QPoint centrePoint = box.center();

    qreal startAngle = angleFromArc(centrePoint, start);
    qreal endAngle = angleFromArc(centrePoint, end);
    qreal spanAngle = angularSpan(startAngle, endAngle);

    m_painter->drawPie(box, startAngle * 16, spanAngle * 16);
}

void OutputPainterStrategy::ellipse(const QRect &box)
{
#if DEBUG_EMFPAINT
    debugVectorImage << box;
#endif

    m_painter->drawEllipse(box);
}

void OutputPainterStrategy::rectangle(const QRect &box)
{
#if DEBUG_EMFPAINT
    debugVectorImage << box;
#endif

    m_painter->drawRect(box);
}

void OutputPainterStrategy::setMapMode(const quint32 mapMode)
{
#if DEBUG_EMFPAINT
    debugVectorImage << "Set map mode:" << mapMode;
#endif

    m_mapMode = (MapMode)mapMode;
}

void OutputPainterStrategy::setBkMode(const quint32 backgroundMode)
{
#if DEBUG_EMFPAINT
    debugVectorImage << backgroundMode;
#endif

    if (backgroundMode == TRANSPARENT) {
        m_painter->setBackgroundMode(Qt::TransparentMode);
    } else if (backgroundMode == OPAQUE) {
        m_painter->setBackgroundMode(Qt::OpaqueMode);
    } else {
        debugVectorImage << "EMR_SETBKMODE: Unexpected value -" << backgroundMode;
        Q_ASSERT(0);
    }
}

void OutputPainterStrategy::setPolyFillMode(const quint32 polyFillMode)
{
#if DEBUG_EMFPAINT
    debugVectorImage << polyFillMode;
#endif

    if (polyFillMode == ALTERNATE) {
        m_fillRule = Qt::OddEvenFill;
    } else if (polyFillMode == WINDING) {
        m_fillRule = Qt::WindingFill;
    } else {
        debugVectorImage << "EMR_SETPOLYFILLMODE: Unexpected value -" << polyFillMode;
        Q_ASSERT(0);
    }
}

void OutputPainterStrategy::setLayout(const quint32 layoutMode)
{
#if DEBUG_EMFPAINT
    debugVectorImage << layoutMode;
#endif

    if (layoutMode == LAYOUT_LTR) {
        m_painter->setLayoutDirection(Qt::LeftToRight);
    } else if (layoutMode == LAYOUT_RTL) {
        m_painter->setLayoutDirection(Qt::RightToLeft);
    } else {
        debugVectorImage << "EMR_SETLAYOUT: Unexpected value -" << layoutMode;
        Q_ASSERT(0);
    }
}

void OutputPainterStrategy::setTextAlign(const quint32 textAlignMode)
{
#if DEBUG_EMFPAINT
    debugVectorImage << textAlignMode;
#endif

    m_textAlignMode = textAlignMode;
}

void OutputPainterStrategy::setTextColor(const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved)
{
    Q_UNUSED(reserved);

#if DEBUG_EMFPAINT
    debugVectorImage << red << green << blue << reserved;
#endif

    m_textPen.setColor(QColor(red, green, blue));
}

void OutputPainterStrategy::setBkColor(const quint8 red, const quint8 green, const quint8 blue, const quint8 reserved)
{
    Q_UNUSED(reserved);

#if DEBUG_EMFPAINT
    debugVectorImage << red << green << blue << reserved;
#endif

    m_painter->setBackground(QBrush(QColor(red, green, blue)));
}

#define DEBUG_TEXTOUT 0

void OutputPainterStrategy::extTextOut(const QRect &bounds, const EmrTextObject &textObject)
{
    const QPoint &referencePoint = textObject.referencePoint();
    const QString &text = textObject.textString();

#if DEBUG_EMFPAINT
    debugVectorImage << "Ref point: " << referencePoint << "options: " << hex << textObject.options() << dec << "rectangle: " << textObject.rectangle()
                     << "text: " << textObject.textString();
#endif

    int x = referencePoint.x();
    int y = referencePoint.y();

    // The TA_UPDATECP flag tells us to use the current position
    if (m_textAlignMode & TA_UPDATECP) {
        // (left, top) position = current logical position
#if DEBUG_EMFPAINT
        debugVectorImage << "TA_UPDATECP: use current logical position";
#endif
        x = m_currentCoords.x();
        y = m_currentCoords.y();
    }

    QFontMetrics fm = m_painter->fontMetrics();
    int textWidth = fm.boundingRect(text).width() + fm.descent(); // fm.width(text) isn't right with Italic text
    int textHeight = fm.height();

    // Make (x, y) be the coordinates of the upper left corner of the
    // rectangle surrounding the text.
    //
    // FIXME: Handle RTL text.

    // Horizontal align.  Default is TA_LEFT.
    if ((m_textAlignMode & TA_HORZMASK) == TA_CENTER)
        x -= (textWidth / 2);
    else if ((m_textAlignMode & TA_HORZMASK) == TA_RIGHT)
        x -= textWidth;

    // Vertical align.  Default is TA_TOP
    if ((m_textAlignMode & TA_VERTMASK) == TA_BASELINE)
        y -= (textHeight - fm.descent());
    else if ((m_textAlignMode & TA_VERTMASK) == TA_BOTTOM) {
        y -= textHeight;
    }

#if DEBUG_EMFPAINT
    debugVectorImage << "textWidth = " << textWidth << "height = " << textHeight;

    debugVectorImage << "font = " << m_painter->font() << "pointSize = " << m_painter->font().pointSize() << "ascent = " << fm.ascent()
                     << "descent = " << fm.descent() << "height = " << fm.height() << "leading = " << fm.leading();
    debugVectorImage << "actual point = " << x << y;
#endif

    // Debug code that paints a rectangle around the output area.
#if DEBUG_TEXTOUT
    m_painter->save();
    m_painter->setWorldTransform(m_outputTransform);
    m_painter->setPen(QPen(Qt::black, 0));
    m_painter->drawRect(bounds);
    m_painter->restore();
#endif

    // Actual painting starts here.
    m_painter->save();

    // Find out how much we have to scale the text to make it fit into
    // the output rectangle.  Normally this wouldn't be necessary, but
    // when fonts are switched, the replacement fonts are sometimes
    // wider than the original fonts.
    QRect worldRect(m_worldTransform.mapRect(QRect(x, y, textWidth, textHeight)));
    // debugVectorImage << "rects:" << QRect(x, y, textWidth, textHeight) << worldRect;
    qreal scaleX = qreal(1.0);
    qreal scaleY = qreal(1.0);
    if (bounds.width() < worldRect.width())
        scaleX = qreal(bounds.width()) / qreal(worldRect.width());
    if (bounds.height() < worldRect.height())
        scaleY = qreal(bounds.height()) / qreal(worldRect.height());
    // debugVectorImage << "scale:" << scaleX << scaleY;

    if (scaleX < qreal(1.0) || scaleY < qreal(1.0)) {
        m_painter->translate(-x, -y);
        m_painter->scale(scaleX, scaleY);
        m_painter->translate(x / scaleX, y / scaleY);
    }

    // Use the special pen defined by mTextPen for text.
    QPen savePen = m_painter->pen();
    m_painter->setPen(m_textPen);
    m_painter->drawText(int(x / scaleX), int(y / scaleY), textWidth, textHeight, Qt::AlignLeft | Qt::AlignTop, text);
    m_painter->setPen(savePen);

    m_painter->restore();
}

void OutputPainterStrategy::moveToEx(const qint32 x, const qint32 y)
{
#if DEBUG_EMFPAINT
    debugVectorImage << x << y;
#endif

    if (m_currentlyBuildingPath)
        m_path->moveTo(QPoint(x, y));
    else
        m_currentCoords = QPoint(x, y);
}

void OutputPainterStrategy::lineTo(const QPoint &finishPoint)
{
#if DEBUG_EMFPAINT
    debugVectorImage << finishPoint;
#endif

    if (m_currentlyBuildingPath)
        m_path->lineTo(finishPoint);
    else {
        m_painter->drawLine(m_currentCoords, finishPoint);
        m_currentCoords = finishPoint;
    }
}

void OutputPainterStrategy::arcTo(const QRect &box, const QPoint &start, const QPoint &end)
{
#if DEBUG_EMFPAINT
    debugVectorImage << box << start << end;
#endif

    QPoint centrePoint = box.center();

    qreal startAngle = angleFromArc(centrePoint, start);
    qreal endAngle = angleFromArc(centrePoint, end);
    qreal spanAngle = angularSpan(startAngle, endAngle);

    m_path->arcTo(box, startAngle, spanAngle);
}

void OutputPainterStrategy::polygon16(const QRect &bounds, const QList<QPoint> points)
{
    Q_UNUSED(bounds);

#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    QVector<QPoint> pointVector = points.toVector();
    m_painter->drawPolygon(pointVector.constData(), pointVector.size(), m_fillRule);
}

void OutputPainterStrategy::polyLine(const QRect &bounds, const QList<QPoint> points)
{
    Q_UNUSED(bounds);

#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    QVector<QPoint> pointVector = points.toVector();
    m_painter->drawPolyline(pointVector.constData(), pointVector.size());
}

void OutputPainterStrategy::polyLine16(const QRect &bounds, const QList<QPoint> points)
{
#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    polyLine(bounds, points);
}

void OutputPainterStrategy::polyPolygon16(const QRect &bounds, const QList<QVector<QPoint>> &points)
{
    Q_UNUSED(bounds);

#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    for (int i = 0; i < points.size(); ++i) {
        m_painter->drawPolygon(points[i].constData(), points[i].size(), m_fillRule);
    }
}

void OutputPainterStrategy::polyPolyLine16(const QRect &bounds, const QList<QVector<QPoint>> &points)
{
    Q_UNUSED(bounds);

#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    for (int i = 0; i < points.size(); ++i) {
        m_painter->drawPolyline(points[i].constData(), points[i].size());
    }
}

void OutputPainterStrategy::polyLineTo16(const QRect &bounds, const QList<QPoint> points)
{
    Q_UNUSED(bounds);

#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    for (int i = 0; i < points.count(); ++i) {
        m_path->lineTo(points[i]);
    }
}

void OutputPainterStrategy::polyBezier16(const QRect &bounds, const QList<QPoint> points)
{
#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    Q_UNUSED(bounds);
    QPainterPath path;
    path.moveTo(points[0]);
    for (int i = 1; i < points.count(); i += 3) {
        path.cubicTo(points[i], points[i + 1], points[i + 2]);
    }
    m_painter->drawPath(path);
}

void OutputPainterStrategy::polyBezierTo16(const QRect &bounds, const QList<QPoint> points)
{
#if DEBUG_EMFPAINT
    debugVectorImage << bounds << points;
#endif

    Q_UNUSED(bounds);
    for (int i = 0; i < points.count(); i += 3) {
        m_path->cubicTo(points[i], points[i + 1], points[i + 2]);
    }
}

void OutputPainterStrategy::fillPath(const QRect &bounds)
{
#if DEBUG_EMFPAINT
    debugVectorImage << bounds;
#endif

    Q_UNUSED(bounds);
    m_painter->fillPath(*m_path, m_painter->brush());
}

void OutputPainterStrategy::strokeAndFillPath(const QRect &bounds)
{
#if DEBUG_EMFPAINT
    debugVectorImage << bounds;
#endif

    Q_UNUSED(bounds);
    m_painter->drawPath(*m_path);
}

void OutputPainterStrategy::strokePath(const QRect &bounds)
{
#if DEBUG_EMFPAINT
    debugVectorImage << bounds;
#endif

    Q_UNUSED(bounds);
    m_painter->strokePath(*m_path, m_painter->pen());
}

void OutputPainterStrategy::setClipPath(const quint32 regionMode)
{
#if DEBUG_EMFPAINT
    debugVectorImage << hex << regionMode << dec;
#endif

    switch (regionMode) {
    case RGN_AND:
        m_painter->setClipPath(*m_path, Qt::IntersectClip);
        break;
        // QT5TODO: T477 Qt::UniteClip got removed, see https://bugreports.qt.io/browse/QTBUG-20140
        //     case RGN_OR:
        //         m_painter->setClipPath( *m_path, Qt::UniteClip );
        //         break;
    case RGN_COPY:
        m_painter->setClipPath(*m_path, Qt::ReplaceClip);
        break;
    default:
        warnVectorImage << "Unexpected / unsupported clip region mode:" << regionMode;
        Q_ASSERT(0);
    }
}

void OutputPainterStrategy::bitBlt(BitBltRecord &bitBltRecord)
{
#if DEBUG_EMFPAINT
    debugVectorImage << bitBltRecord.xDest() << bitBltRecord.yDest() << bitBltRecord.cxDest() << bitBltRecord.cyDest() << hex << bitBltRecord.rasterOperation()
                     << dec << bitBltRecord.bkColorSrc();
#endif

    QRect target(bitBltRecord.xDest(), bitBltRecord.yDest(), bitBltRecord.cxDest(), bitBltRecord.cyDest());
    // 0x00f00021 is the PatCopy raster operation which just fills a rectangle with a brush.
    // This seems to be the most common one.
    //
    // FIXME: Implement the rest of the raster operations.
    if (bitBltRecord.rasterOperation() == 0x00f00021) {
        // Would have been nice if we didn't have to pull out the
        // brush to use it with fillRect()...
        QBrush brush = m_painter->brush();
        m_painter->fillRect(target, brush);
    } else if (bitBltRecord.hasImage()) {
        m_painter->drawImage(target, bitBltRecord.image());
    }
}

void OutputPainterStrategy::setStretchBltMode(const quint32 stretchMode)
{
#if DEBUG_EMFPAINT
    debugVectorImage << hex << stretchMode << dec;
#endif

    switch (stretchMode) {
    case 0x01:
        debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ANDSCANS";
        break;
    case 0x02:
        debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_ORSCANS";
        break;
    case 0x03:
        debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_DELETESCANS";
        break;
    case 0x04:
        debugVectorImage << "EMR_STRETCHBLTMODE: STRETCH_HALFTONE";
        break;
    default:
        debugVectorImage << "EMR_STRETCHBLTMODE - unknown stretch mode:" << stretchMode;
    }
}

void OutputPainterStrategy::stretchDiBits(StretchDiBitsRecord &record)
{
#if DEBUG_EMFPAINT
    debugVectorImage << "Bounds:    " << record.bounds();
    debugVectorImage << "Dest rect: " << record.xDest() << record.yDest() << record.cxDest() << record.cyDest();
    debugVectorImage << "Src rect:  " << record.xSrc() << record.ySrc() << record.cxSrc() << record.cySrc();
    debugVectorImage << "Raster op: " << hex << record.rasterOperation() << dec;
    //<< record.bkColorSrc();
    debugVectorImage << "usageSrc: " << record.usageSrc();
#endif

    QPoint targetPosition(record.xDest(), record.yDest());
    QSize targetSize(record.cxDest(), record.cyDest());

    QPoint sourcePosition(record.xSrc(), record.ySrc());
    QSize sourceSize(record.cxSrc(), record.cySrc());

    // special cases, from [MS-EMF] Section 2.3.1.7:
    // "This record specifies a mirror-image copy of the source bitmap to the
    // destination if the signs of the height or width fields differ. That is,
    // if cxSrc and cxDest have different signs, this record specifies a mirror
    // image of the source bitmap along the x-axis. If cySrc and cyDest have
    // different signs, this record specifies a mirror image of the source
    //  bitmap along the y-axis."
    QRect target(targetPosition, targetSize);
    QRect source(sourcePosition, sourceSize);
#if DEBUG_EMFPAINT
    // debugVectorImage << "image size" << record.image()->size();
    debugVectorImage << "Before transformation:";
    debugVectorImage << "    target" << target;
    debugVectorImage << "    source" << source;
#endif
    if (source.width() < 0 && target.width() > 0) {
        sourceSize.rwidth() *= -1;
        sourcePosition.rx() -= sourceSize.width();
        source = QRect(sourcePosition, sourceSize);
    }
    if (source.width() > 0 && target.width() < 0) {
        targetSize.rwidth() *= -1;
        targetPosition.rx() -= targetSize.width();
        target = QRect(targetPosition, targetSize);
    }
    if (source.height() < 0 && target.height() > 0) {
        sourceSize.rheight() *= -1;
        sourcePosition.ry() -= sourceSize.height();
        source = QRect(sourcePosition, sourceSize);
    }
    if (source.height() > 0 && target.height() < 0) {
        targetSize.rheight() *= -1;
        targetPosition.ry() -= targetSize.height();
        target = QRect(targetPosition, targetSize);
    }
#if DEBUG_EMFPAINT
    debugVectorImage << "After transformation:";
    debugVectorImage << "    target" << target;
    debugVectorImage << "    source" << source;
    QImage image = record.image();
    debugVectorImage << "Image" << image.size();

#endif

    QPainter::RenderHints oldRenderHints = m_painter->renderHints();
    QPainter::CompositionMode oldCompMode = m_painter->compositionMode();
    m_painter->setRenderHints({}); // Antialiasing makes composition modes invalid
    m_painter->setCompositionMode(rasteropToQtComposition(record.rasterOperation()));
    m_painter->drawImage(target, record.image(), source);
    m_painter->setCompositionMode(oldCompMode);
    m_painter->setRenderHints(oldRenderHints);
}

// ----------------------------------------------------------------
//                         Private functions

void OutputPainterStrategy::printPainterTransform(const char *leadText)
{
    QTransform transform;

    recalculateWorldTransform();

    debugVectorImage << leadText << "world transform " << m_worldTransform << "incl output transform: " << m_painter->transform();
}

qreal OutputPainterStrategy::angleFromArc(const QPoint &centrePoint, const QPoint &radialPoint)
{
    double dX = radialPoint.x() - centrePoint.x();
    double dY = centrePoint.y() - radialPoint.y();
    // Qt angles are in degrees. atan2 returns radians
    return (atan2(dY, dX) * 180 / M_PI);
}

qreal OutputPainterStrategy::angularSpan(const qreal startAngle, const qreal endAngle)
{
    qreal spanAngle = endAngle - startAngle;

    if (spanAngle <= 0) {
        spanAngle += 360;
    }

    return spanAngle;
}

QFont::Weight OutputPainterStrategy::convertFontWeight(quint32 emfWeight)
{
    // FIXME: See how it's done in the wmf library and check if this is suitable here.

    if (emfWeight == 0) {
        return QFont::Normal;
    } else if (emfWeight <= 200) {
        return QFont::Light;
    } else if (emfWeight <= 450) {
        return QFont::Normal;
    } else if (emfWeight <= 650) {
        return QFont::DemiBold;
    } else if (emfWeight <= 850) {
        return QFont::Bold;
    } else {
        return QFont::Black;
    }
}

static QPainter::CompositionMode rasteropToQtComposition(long rop)
{
    // Code copied from filters/libkowmf/qwmf.cc
    // FIXME: Should be cleaned up

    /* 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::RasterOp_SourceOrDestination}, // OrROP
        {0x008800C6, QPainter::RasterOp_SourceAndDestination}, // AndROP
        {0x00660046, QPainter::RasterOp_SourceXorDestination}, // XorROP
        // ----------------------------------------------------------------
        // FIXME: Checked above this, below is still todo
        // ----------------------------------------------------------------
        {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 == rop)
            break;

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

} // xnamespace...
