# Source code for qwt.plot_renderer

    # -*- coding: utf-8 -*-
    #
    # Licensed under the terms of the Qwt License
    # Copyright (c) 2002 Uwe Rathmann, for the original C++ code
    # Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
    # (see LICENSE file for more details)

    """
    QwtPlotRenderer
    ---------------

    .. autoclass:: QwtPlotRenderer
       :members:
    """

    import math
    import os.path as osp

    from qtpy.compat import getsavefilename
    from qtpy.QtCore import QObject, QRect, QRectF, QSizeF, Qt
    from qtpy.QtGui import (
        QColor,
        QImage,
        QImageWriter,
        QPageSize,
        QPaintDevice,
        QPainter,
        QPainterPath,
        QPalette,
        QPen,
        QTransform,
    )
    from qtpy.QtPrintSupport import QPrinter
    from qtpy.QtSvg import QSvgGenerator
    from qtpy.QtWidgets import QFileDialog

    from qwt.painter import QwtPainter
    from qwt.plot import QwtPlot
    from qwt.plot_layout import QwtPlotLayout
    from qwt.scale_draw import QwtScaleDraw
    from qwt.scale_map import QwtScaleMap

    def qwtCanvasClip(canvas, canvasRect):
        """
        The clip region is calculated in integers
        To avoid too much rounding errors better
        calculate it in target device resolution
        """
        x1 = math.ceil(canvasRect.left())
        x2 = math.floor(canvasRect.right())
        y1 = math.ceil(canvasRect.top())
        y2 = math.floor(canvasRect.bottom())
        r = QRect(x1, y1, x2 - x1 - 1, y2 - y1 - 1)
        return canvas.borderPath(r)

    class QwtPlotRenderer_PrivateData(QObject):
        def __init__(self):
            QObject.__init__(self)

            self.discardFlags = QwtPlotRenderer.DiscardNone
            self.layoutFlags = QwtPlotRenderer.DefaultLayout

    class QwtPlotRenderer(QObject):
        """
        Renderer for exporting a plot to a document, a printer
        or anything else, that is supported by QPainter/QPaintDevice

        Discard flags:

          * `QwtPlotRenderer.DiscardNone`: Render all components of the plot
          * `QwtPlotRenderer.DiscardBackground`: Don't render the background of the plot
          * `QwtPlotRenderer.DiscardTitle`: Don't render the title of the plot
          * `QwtPlotRenderer.DiscardLegend`: Don't render the legend of the plot
          * `QwtPlotRenderer.DiscardCanvasBackground`: Don't render the background of the canvas
          * `QwtPlotRenderer.DiscardFooter`: Don't render the footer of the plot
          * `QwtPlotRenderer.DiscardCanvasFrame`: Don't render the frame of the canvas

        .. note::

            The `QwtPlotRenderer.DiscardCanvasFrame` flag has no effect when using
            style sheets, where the frame is part of the background

        Layout flags:

          * `QwtPlotRenderer.DefaultLayout`: Use the default layout as on screen
          * `QwtPlotRenderer.FrameWithScales`: Instead of the scales a box is painted around the plot canvas, where the scale ticks are aligned to.
        """

        # enum DiscardFlag
        DiscardNone = 0x00
        DiscardBackground = 0x01
        DiscardTitle = 0x02
        DiscardLegend = 0x04
        DiscardCanvasBackground = 0x08
        DiscardFooter = 0x10
        DiscardCanvasFrame = 0x20

        # enum LayoutFlag
        DefaultLayout = 0x00
        FrameWithScales = 0x01

        def __init__(self, parent=None):
            QObject.__init__(self, parent)
            self.__data = QwtPlotRenderer_PrivateData()

        def setDiscardFlag(self, flag, on=True):
            """
            Change a flag, indicating what to discard from rendering

            :param int flag: Flag to change
            :param bool on: On/Off

            .. seealso::

                :py:meth:`testDiscardFlag()`, :py:meth:`setDiscardFlags()`,
                :py:meth:`discardFlags()`
            """
            if on:
                self.__data.discardFlags |= flag
            else:
                self.__data.discardFlags &= ~flag

        def testDiscardFlag(self, flag):
            """
            :param int flag: Flag to be tested
            :return: True, if flag is enabled.

            .. seealso::

                :py:meth:`setDiscardFlag()`, :py:meth:`setDiscardFlags()`,
                :py:meth:`discardFlags()`
            """
            return self.__data.discardFlags & flag

        def setDiscardFlags(self, flags):
            """
            Set the flags, indicating what to discard from rendering

            :param int flags: Flags

            .. seealso::

                :py:meth:`testDiscardFlag()`, :py:meth:`setDiscardFlag()`,
                :py:meth:`discardFlags()`
            """
            self.__data.discardFlags = flags

        def discardFlags(self):
            """
            :return: Flags, indicating what to discard from rendering

            .. seealso::

                :py:meth:`setDiscardFlag()`, :py:meth:`setDiscardFlags()`,
                :py:meth:`testDiscardFlag()`
            """
            return self.__data.discardFlags

        def setLayoutFlag(self, flag, on=True):
            """
            Change a layout flag

            :param int flag: Flag to change

            .. seealso::

                :py:meth:`testLayoutFlag()`, :py:meth:`setLayoutFlags()`,
                :py:meth:`layoutFlags()`
            """
            if on:
                self.__data.layoutFlags |= flag
            else:
                self.__data.layoutFlags &= ~flag

        def testLayoutFlag(self, flag):
            """
            :param int flag: Flag to be tested
            :return: True, if flag is enabled.

            .. seealso::

                :py:meth:`setLayoutFlag()`, :py:meth:`setLayoutFlags()`,
                :py:meth:`layoutFlags()`
            """
            return self.__data.layoutFlags & flag

        def setLayoutFlags(self, flags):
            """
            Set the layout flags

            :param int flags: Flags

            .. seealso::

                :py:meth:`setLayoutFlag()`, :py:meth:`testLayoutFlag()`,
                :py:meth:`layoutFlags()`
            """
            self.__data.layoutFlags = flags

        def layoutFlags(self):
            """
            :return: Layout flags

            .. seealso::

                :py:meth:`setLayoutFlags()`, :py:meth:`setLayoutFlag()`,
                :py:meth:`testLayoutFlag()`
            """
            return self.__data.layoutFlags

        def renderDocument(
            self, plot, filename, sizeMM=(300, 200), resolution=85, format_=None
        ):
            """
            Render a plot to a file

            The format of the document will be auto-detected from the
            suffix of the file name.

            :param qwt.plot.QwtPlot plot: Plot widget
            :param str fileName: Path of the file, where the document will be stored
            :param QSizeF sizeMM: Size for the document in millimeters
            :param int resolution: Resolution in dots per Inch (dpi)
            """
            if isinstance(sizeMM, tuple):
                sizeMM = QSizeF(*sizeMM)
            if format_ is None:
                ext = osp.splitext(filename)[1]
                if not ext:
                    raise TypeError("Unable to determine target format from filename")
                format_ = ext[1:]
            if plot is None or sizeMM.isEmpty() or resolution <= 0:
                return
            title = plot.title().text()
            if not title:
                title = "Plot Document"
            mmToInch = 1.0 / 25.4
            size = sizeMM * mmToInch * resolution
            documentRect = QRectF(0.0, 0.0, size.width(), size.height())
            fmt = format_.lower()
            if fmt in ("pdf", "ps"):
                printer = QPrinter()
                if fmt == "pdf":
                    try:
                        printer.setOutputFormat(QPrinter.PdfFormat)
                    except AttributeError:
                        # PyQt6 on Linux
                        printer.setPrinterName("")
                else:
                    printer.setOutputFormat(QPrinter.PostScriptFormat)
                try:
                    printer.setColorMode(QPrinter.Color)
                except AttributeError:
                    # PyQt6 on Linux
                    pass
                printer.setFullPage(True)
                printer.setPageSize(QPageSize(sizeMM, QPageSize.Millimeter))
                printer.setDocName(title)
                printer.setOutputFileName(filename)
                printer.setResolution(resolution)
                painter = QPainter(printer)
                self.render(plot, painter, documentRect)
                painter.end()
            elif fmt == "svg":
                generator = QSvgGenerator()
                generator.setTitle(title)
                generator.setFileName(filename)
                generator.setResolution(resolution)
                generator.setViewBox(documentRect)
                painter = QPainter(generator)
                self.render(plot, painter, documentRect)
                painter.end()
            elif fmt in QImageWriter.supportedImageFormats():
                imageRect = documentRect.toRect()
                dotsPerMeter = int(round(resolution * mmToInch * 1000.0))
                image = QImage(imageRect.size(), QImage.Format_ARGB32)
                image.setDotsPerMeterX(dotsPerMeter)
                image.setDotsPerMeterY(dotsPerMeter)
                image.fill(QColor(Qt.white).rgb())
                painter = QPainter(image)
                self.render(plot, painter, imageRect)
                painter.end()
                image.save(filename, fmt)
            else:
                raise TypeError("Unsupported file format '%s'" % fmt)

        def renderTo(self, plot, dest):
            """
            Render a plot to a file

            Supported formats are:

              - pdf: Portable Document Format PDF
              - ps: Postcript
              - svg: Scalable Vector Graphics SVG
              - all image formats supported by Qt, see QImageWriter.supportedImageFormats()

            Scalable vector graphic formats like PDF or SVG are superior to
            raster graphics formats.

            :param qwt.plot.QwtPlot plot: Plot widget
            :param dest: QPaintDevice, QPrinter or QSvgGenerator instance

            .. seealso::

                :py:meth:`render()`,
                :py:meth:`qwt.painter.QwtPainter.setRoundingAlignment()`
            """
            if isinstance(dest, QPaintDevice):
                w = dest.width()
                h = dest.height()
                rect = QRectF(0, 0, w, h)
            elif isinstance(dest, QPrinter):
                w = dest.width()
                h = dest.height()
                rect = QRectF(0, 0, w, h)
                aspect = rect.width() / rect.height()
                if aspect < 1.0:
                    rect.setHeight(aspect * rect.width())
            elif isinstance(dest, QSvgGenerator):
                rect = dest.viewBoxF()
                if rect.isEmpty():
                    rect.setRect(0, 0, dest.width(), dest.height())
                if rect.isEmpty():
                    rect.setRect(0, 0, 800, 600)
            else:
                raise TypeError("Unsupported destination type %s" % type(dest))
            p = QPainter(dest)
            self.render(plot, p, rect)

        def render(self, plot, painter, plotRect):
            """
            Paint the contents of a QwtPlot instance into a given rectangle.

            :param qwt.plot.QwtPlot plot: Plot to be rendered
            :param QPainter painter: Painter
            :param str format: Format for the document
            :param QRectF plotRect: Bounding rectangle

            .. seealso::

                :py:meth:`renderDocument()`, :py:meth:`renderTo()`,
                :py:meth:`qwt.painter.QwtPainter.setRoundingAlignment()`
            """
            if (
                painter == 0
                or not painter.isActive()
                or not plotRect.isValid()
                or plot.size().isNull()
            ):
                return
            if not self.__data.discardFlags & self.DiscardBackground:
                QwtPainter.drawBackground(painter, plotRect, plot)

            #  The layout engine uses the same methods as they are used
            #  by the Qt layout system. Therefore we need to calculate the
            #  layout in screen coordinates and paint with a scaled painter.
            transform = QTransform()
            transform.scale(
                float(painter.device().logicalDpiX()) / plot.logicalDpiX(),
                float(painter.device().logicalDpiY()) / plot.logicalDpiY(),
            )

            invtrans, _ok = transform.inverted()
            layoutRect = invtrans.mapRect(plotRect)
            if not (self.__data.discardFlags & self.DiscardBackground):
                mg = plot.contentsMargins()
                layoutRect.adjust(mg.left(), mg.top(), -mg.right(), -mg.bottom())

            layout = plot.plotLayout()
            baseLineDists = canvasMargins = [None] * len(QwtPlot.AXES)

            for axisId in QwtPlot.AXES:
                canvasMargins[axisId] = layout.canvasMargin(axisId)
                if self.__data.layoutFlags & self.FrameWithScales:
                    scaleWidget = plot.axisWidget(axisId)
                    if scaleWidget:
                        mgn = scaleWidget.contentsMargins()
                        baseLineDists[axisId] = max(
                            [mgn.left(), mgn.top(), mgn.right(), mgn.bottom()]
                        )
                        scaleWidget.setMargin(0)
                    if not plot.axisEnabled(axisId):
                        #  When we have a scale the frame is painted on
                        #  the position of the backbone - otherwise we
                        #  need to introduce a margin around the canvas
                        if axisId == QwtPlot.yLeft:
                            layoutRect.adjust(1, 0, 0, 0)
                        elif axisId == QwtPlot.yRight:
                            layoutRect.adjust(0, 0, -1, 0)
                        elif axisId == QwtPlot.xTop:
                            layoutRect.adjust(0, 1, 0, 0)
                        elif axisId == QwtPlot.xBottom:
                            layoutRect.adjust(0, 0, 0, -1)

            #  Calculate the layout for the document.
            layoutOptions = QwtPlotLayout.IgnoreScrollbars

            if (
                self.__data.layoutFlags & self.FrameWithScales
                or self.__data.discardFlags & self.DiscardCanvasFrame
            ):
                layoutOptions |= QwtPlotLayout.IgnoreFrames

            if self.__data.discardFlags & self.DiscardLegend:
                layoutOptions |= QwtPlotLayout.IgnoreLegend
            if self.__data.discardFlags & self.DiscardTitle:
                layoutOptions |= QwtPlotLayout.IgnoreTitle
            if self.__data.discardFlags & self.DiscardFooter:
                layoutOptions |= QwtPlotLayout.IgnoreFooter

            layout.activate(plot, layoutRect, layoutOptions)

            maps = self.buildCanvasMaps(plot, layout.canvasRect())
            if self.updateCanvasMargins(plot, layout.canvasRect(), maps):
                #  recalculate maps and layout, when the margins
                #  have been changed
                layout.activate(plot, layoutRect, layoutOptions)
                maps = self.buildCanvasMaps(plot, layout.canvasRect())

            painter.save()
            painter.setWorldTransform(transform, True)

            self.renderCanvas(plot, painter, layout.canvasRect(), maps)

            if (
                not self.__data.discardFlags & self.DiscardTitle
            ) and plot.titleLabel().text():
                self.renderTitle(plot, painter, layout.titleRect())

            if (
                not self.__data.discardFlags & self.DiscardFooter
            ) and plot.titleLabel().text():
                self.renderFooter(plot, painter, layout.footerRect())

            if (
                not self.__data.discardFlags & self.DiscardLegend
            ) and plot.titleLabel().text():
                self.renderLegend(plot, painter, layout.legendRect())

            for axisId in QwtPlot.AXES:
                scaleWidget = plot.axisWidget(axisId)
                if scaleWidget:
                    mgn = scaleWidget.contentsMargins()
                    baseDist = max([mgn.left(), mgn.top(), mgn.right(), mgn.bottom()])
                    startDist, endDist = scaleWidget.getBorderDistHint()
                    self.renderScale(
                        plot,
                        painter,
                        axisId,
                        startDist,
                        endDist,
                        baseDist,
                        layout.scaleRect(axisId),
                    )

            painter.restore()

            for axisId in QwtPlot.AXES:
                if self.__data.layoutFlags & self.FrameWithScales:
                    scaleWidget = plot.axisWidget(axisId)
                    if scaleWidget:
                        scaleWidget.setMargin(baseLineDists[axisId])
                layout.setCanvasMargin(canvasMargins[axisId])

            layout.invalidate()

        def renderTitle(self, plot, painter, rect):
            """
            Render the title into a given rectangle.

            :param qwt.plot.QwtPlot plot: Plot widget
            :param QPainter painter: Painter
            :param QRectF rect: Bounding rectangle
            """
            painter.setFont(plot.titleLabel().font())
            color = plot.titleLabel().palette().color(QPalette.Active, QPalette.Text)
            painter.setPen(color)
            plot.titleLabel().text().draw(painter, rect)

        def renderLegend(self, plot, painter, rect):
            """
            Render the legend into a given rectangle.

            :param qwt.plot.QwtPlot plot: Plot widget
            :param QPainter painter: Painter
            :param QRectF rect: Bounding rectangle
            """
            if plot.legend():
                fillBackground = not self.__data.discardFlags & self.DiscardBackground
                plot.legend().renderLegend(painter, rect, fillBackground)

        def renderScale(self, plot, painter, axisId, startDist, endDist, baseDist, rect):
            """
            Paint a scale into a given rectangle.
            Paint the scale into a given rectangle.

            :param qwt.plot.QwtPlot plot: Plot widget
            :param QPainter painter: Painter
            :param int axisId: Axis
            :param int startDist: Start border distance
            :param int endDist: End border distance
            :param int baseDist: Base distance
            :param QRectF rect: Bounding rectangle
            """
            if not plot.axisEnabled(axisId):
                return
            scaleWidget = plot.axisWidget(axisId)
            if scaleWidget.isColorBarEnabled() and scaleWidget.colorBarWidth() > 0:
                scaleWidget.drawColorBar(painter, scaleWidget.colorBarRect(rect))
                baseDist += scaleWidget.colorBarWidth() + scaleWidget.spacing()
            painter.save()
            if axisId == QwtPlot.yLeft:
                x = rect.right() - 1.0 - baseDist
                y = rect.y() + startDist
                w = rect.height() - startDist - endDist
                align = QwtScaleDraw.LeftScale
            elif axisId == QwtPlot.yRight:
                x = rect.left() + baseDist
                y = rect.y() + startDist
                w = rect.height() - startDist - endDist
                align = QwtScaleDraw.RightScale
            elif axisId == QwtPlot.xTop:
                x = rect.left() + startDist
                y = rect.bottom() - 1.0 - baseDist
                w = rect.width() - startDist - endDist
                align = QwtScaleDraw.TopScale
            else:  # QwtPlot.xBottom
                x = rect.left() + startDist
                y = rect.top() + baseDist
                w = rect.width() - startDist - endDist
                align = QwtScaleDraw.BottomScale

            scaleWidget.drawTitle(painter, align, rect)
            painter.setFont(scaleWidget.font())
            sd = scaleWidget.scaleDraw()
            sdPos = sd.pos()
            sdLength = sd.length()
            sd.move(x, y)
            sd.setLength(w)
            palette = scaleWidget.palette()
            palette.setCurrentColorGroup(QPalette.Active)
            sd.draw(painter, palette)
            sd.move(sdPos)
            sd.setLength(sdLength)
            painter.restore()

        def renderCanvas(self, plot, painter, canvasRect, maps):
            """
            Render the canvas into a given rectangle.

            :param qwt.plot.QwtPlot plot: Plot widget
            :param QPainter painter: Painter
            :param QRectF rect: Bounding rectangle
            :param qwt.scale_map.QwtScaleMap maps: mapping between plot and paint device coordinates
            """
            canvas = plot.canvas()
            r = canvasRect.adjusted(0.0, 0.0, -1.0, 1.0)
            if self.__data.layoutFlags & self.FrameWithScales:
                painter.save()
                r.adjust(-1.0, -1.0, 1.0, 1.0)
                painter.setPen(QPen(Qt.black))
                if not (self.__data.discardFlags & self.DiscardCanvasBackground):
                    bgBrush = canvas.palette().brush(plot.backgroundRole())
                    painter.setBrush(bgBrush)
                painter.drawRect(r)
                painter.restore()
                painter.save()
                painter.setClipRect(canvasRect)
                plot.drawItems(painter, canvasRect, maps)
                painter.restore()
            elif canvas.testAttribute(Qt.WA_StyledBackground):
                clipPath = QPainterPath()
                painter.save()
                if not self.__data.discardFlags & self.DiscardCanvasBackground:
                    QwtPainter.drawBackground(painter, r, canvas)
                    clipPath = qwtCanvasClip(canvas, canvasRect)
                painter.restore()
                painter.save()
                if clipPath.isEmpty():
                    painter.setClipRect(canvasRect)
                else:
                    painter.setClipPath(clipPath)
                plot.drawItems(painter, canvasRect, maps)
                painter.restore()
            else:
                clipPath = QPainterPath()
                frameWidth = 0
                if not self.__data.discardFlags & self.DiscardCanvasFrame:
                    frameWidth = canvas.frameWidth()
                    clipPath = qwtCanvasClip(canvas, canvasRect)
                innerRect = canvasRect.adjusted(
                    frameWidth, frameWidth, -frameWidth, -frameWidth
                )
                painter.save()
                if clipPath.isEmpty():
                    painter.setClipRect(innerRect)
                else:
                    painter.setClipPath(clipPath)
                if not self.__data.discardFlags & self.DiscardCanvasBackground:
                    QwtPainter.drawBackground(painter, innerRect, canvas)
                plot.drawItems(painter, innerRect, maps)
                painter.restore()
                if frameWidth > 0:
                    painter.save()
                    frameStyle = canvas.frameShadow() | canvas.frameShape()
                    radius = canvas.borderRadius()
                    if radius > 0.0:
                        QwtPainter.drawRoundedFrame(
                            painter,
                            canvasRect,
                            radius,
                            radius,
                            canvas.palette(),
                            frameWidth,
                            frameStyle,
                        )
                    else:
                        midLineWidth = canvas.midLineWidth()
                        QwtPainter.drawFrame(
                            painter,
                            canvasRect,
                            canvas.palette(),
                            canvas.foregroundRole(),
                            frameWidth,
                            midLineWidth,
                            frameStyle,
                        )
                    painter.restore()

        def buildCanvasMaps(self, plot, canvasRect):
            """
            Calculated the scale maps for rendering the canvas

            :param qwt.plot.QwtPlot plot: Plot widget
            :param QRectF canvasRect: Target rectangle
            :return: Calculated scale maps
            """
            maps = []
            for axisId in QwtPlot.AXES:
                map_ = QwtScaleMap()
                map_.setTransformation(plot.axisScaleEngine(axisId).transformation())
                sd = plot.axisScaleDiv(axisId)
                map_.setScaleInterval(sd.lowerBound(), sd.upperBound())
                if plot.axisEnabled(axisId):
                    s = plot.axisWidget(axisId)
                    scaleRect = plot.plotLayout().scaleRect(axisId)
                    if axisId in (QwtPlot.xTop, QwtPlot.xBottom):
                        from_ = scaleRect.left() + s.startBorderDist()
                        to = scaleRect.right() - s.endBorderDist()
                    else:
                        from_ = scaleRect.bottom() - s.endBorderDist()
                        to = scaleRect.top() + s.startBorderDist()
                else:
                    margin = 0
                    if not plot.plotLayout().alignCanvasToScale(axisId):
                        margin = plot.plotLayout().canvasMargin(axisId)
                    if axisId in (QwtPlot.yLeft, QwtPlot.yRight):
                        from_ = canvasRect.bottom() - margin
                        to = canvasRect.top() + margin
                    else:
                        from_ = canvasRect.left() + margin
                        to = canvasRect.right() - margin
                map_.setPaintInterval(from_, to)
                maps.append(map_)
            return maps

        def updateCanvasMargins(self, plot, canvasRect, maps):
            margins = plot.getCanvasMarginsHint(maps, canvasRect)
            marginsChanged = False
            for axisId in QwtPlot.AXES:
                if margins[axisId] >= 0.0:
                    m = math.ceil(margins[axisId])
                    plot.plotLayout().setCanvasMargin(m, axisId)
                    marginsChanged = True
            return marginsChanged

        def exportTo(self, plot, documentname, sizeMM=None, resolution=85):
            """
            Execute a file dialog and render the plot to the selected file

            :param qwt.plot.QwtPlot plot: Plot widget
            :param str documentName: Default document name
            :param QSizeF sizeMM: Size for the document in millimeters
            :param int resolution: Resolution in dots per Inch (dpi)
            :return: True, when exporting was successful

            .. seealso::

                :py:meth:`renderDocument()`
            """
            if plot is None:
                return
            if sizeMM is None:
                sizeMM = QSizeF(300, 200)
            filename = documentname
            imageFormats = QImageWriter.supportedImageFormats()
            filter_ = [
                "PDF documents (*.pdf)",
                "SVG documents (*.svg)",
                "Postscript documents (*.ps)",
            ]
            if imageFormats:
                imageFilter = "Images"
                imageFilter += " ("
                for idx, fmt in enumerate(imageFormats):
                    if idx > 0:
                        imageFilter += " "
                    imageFilter += "*." + str(fmt)
                imageFilter += ")"
                filter_ += [imageFilter]
            filename, _s = getsavefilename(
                plot,
                "Export File Name",
                filename,
                ";;".join(filter_),
                options=QFileDialog.DontConfirmOverwrite,
            )
            if not filename:
                return False
            self.renderDocument(plot, filename, sizeMM, resolution)
            return True
            return True