
#ifndef NO_QT

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <float.h>
#include <map>
#include <memory>
#include <stack>

#include <QtGlobal>
#include <QDebug>

#endif

#include "gks.h"
#include "gkscore.h"

#ifdef __cplusplus
extern "C" {
#endif

#ifdef _WIN32

#include <windows.h>
#ifndef DLLEXPORT
#define DLLEXPORT __declspec(dllexport)
#endif

#endif

DLLEXPORT void QT_PLUGIN_ENTRY_NAME(int fctid, int dx, int dy, int dimx, int *i_arr, int len_f_arr_1, double *f_arr_1,
                                    int len_f_arr_2, double *f_arr_2, int len_c_arr, char *c_arr, void **ptr);

#ifdef __cplusplus
}
#endif

#ifndef GKS_UNUSED
#define GKS_UNUSED(x) (void)(x)
#endif

#if !(defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND))
#define NO_EXCEPTIONS
#endif

#ifndef NO_QT

#define MAX_POINTS 2048
#define MAX_POINTS_PERFORMANCE_THRESHOLD 500
#define MAX_POLYGON 32
#define MAX_SELECTIONS 100
#define PATTERNS 120
#define HATCH_STYLE 108

#define DrawBorder 0

#define RESOLVE(arg, type, nbytes) \
  arg = (type *)(s + sp);          \
  sp += nbytes

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

#define MAX_TNR 9

#define WC_to_NDC(xw, yw, tnr, xn, yn) \
  xn = a[tnr] * (xw) + b[tnr];         \
  yn = c[tnr] * (yw) + d[tnr]

#define WC_to_NDC_rel(xw, yw, tnr, xn, yn) \
  xn = a[tnr] * (xw);                      \
  yn = c[tnr] * (yw)

#define NDC_to_DC(xn, yn, xd, yd) \
  xd = (p->a * (xn) + p->b);      \
  yd = (p->c * (yn) + p->d);

#define DC_to_NDC(xd, yd, xn, yn) \
  xn = ((xd) - p->b) / p->a;      \
  yn = ((yd) - p->d) / p->c;

#define CharXform(xrel, yrel, x, y)                    \
  x = cos(p->alpha) * (xrel) - sin(p->alpha) * (yrel); \
  y = sin(p->alpha) * (xrel) + cos(p->alpha) * (yrel);

#define nint(a) ((int)(a + 0.5))

#ifndef min
#define min(a, b) (((a) < (b)) ? (a) : (b))
#endif

#ifndef max
#define max(a, b) (((a) > (b)) ? (a) : (b))
#endif

static gks_state_list_t gkss_, *gkss = &gkss_;

static double a[MAX_TNR], b[MAX_TNR], c[MAX_TNR], d[MAX_TNR];

struct bounding_struct
{
  double x_min, x_max;
  double y_min, y_max;
  void (*fun_call)(int, double, double, double, double);
  int item_id;
};

class ProxyPainter;

typedef struct ws_state_list_t
{
  gks_display_list_t dl;
  QPaintDevice *paint_device;
  QPixmap *pixmap;
  QPixmap *selection;
  std::unique_ptr<ProxyPainter> painter;
  int state, wtype;
  int device_dpi_x, device_dpi_y;
  bool has_user_defined_device_pixel_ratio;
  double device_pixel_ratio;
  double mwidth, mheight;
  int width, height;
  double a, b, c, d;
  double window[4], viewport[4];
  double nominal_size;
  QRectF rect[MAX_TNR];
  QColor rgb[MAX_COLOR + 1];
  int transparency;
  QPolygonF *points;
  int npoints, max_points;
  QPolygonF *polygon;
  int max_polygon;
  QFont *font;
  int family, capheight;
  double alpha, angle;
  QPixmap *pattern[PATTERNS];
  int pcolor[PATTERNS];
  bool empty;
  bool prevent_resize_by_dl;
  bool window_stays_on_top;
  bool interp_was_called;
  bool partial_draw;

  void (*memory_plugin)(int, int, int, int, int *, int, double *, int, double *, int, char *, void **);
  bool memory_plugin_initialised;
  int memory_plugin_wstype;
  void *memory_plugin_ws_state_list;
  int *memory_plugin_mem_ptr;
  char *memory_plugin_mem_path;
#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
  std::stack<bounding_struct> bounding_stack;
#endif
  void (*mask_callback)(unsigned int, unsigned int, unsigned int *);
  void (*partial_drawing_callback)(int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int *);
} ws_state_list;

static ws_state_list p_, *p = &p_;

static int fontfile = 0;

static const char *fonts[] = {
    "Times",          "Arial",       "Courier", "Open Symbol", "Bookman Old Style", "Century Schoolbook",
    "Century Gothic", "Book Antiqua"};

static double capheights[29] = {0.662, 0.660, 0.681, 0.662, 0.729, 0.729, 0.729, 0.729, 0.583, 0.583,
                                0.583, 0.583, 0.667, 0.681, 0.681, 0.681, 0.681, 0.722, 0.722, 0.722,
                                0.722, 0.739, 0.739, 0.739, 0.739, 0.694, 0.693, 0.683, 0.683};

static int map[32] = {22, 9,  5, 14, 18, 26, 13, 1, 24, 11, 7, 16, 20, 28, 13, 3,
                      23, 10, 6, 15, 19, 27, 13, 2, 25, 12, 8, 17, 21, 29, 13, 4};

int symbol2utf[256] = {
    0,    1,    2,    3,    4,    5,    6,    7,    8,    9,    10,   11,   12,   13,    14,   15,   16,    17,   18,
    18,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,   32,    33,   8704, 35,    8707, 37,
    38,   8715, 40,   41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,    52,   53,   54,    55,   56,
    57,   58,   59,   60,   61,   62,   63,   8773, 913,  914,  935,  916,  917,  934,   915,  919,  921,   977,  922,
    923,  924,  925,  927,  928,  920,  929,  931,  932,  933,  962,  937,  926,  936,   918,  91,   8756,  93,   8869,
    95,   8254, 945,  946,  967,  948,  949,  966,  947,  951,  953,  981,  954,  955,   956,  957,  959,   960,  952,
    961,  963,  964,  965,  982,  969,  958,  968,  950,  123,  124,  125,  126,  127,   128,  129,  130,   131,  132,
    133,  134,  135,  136,  137,  138,  139,  140,  141,  142,  143,  144,  145,  146,   147,  148,  149,   150,  151,
    152,  153,  154,  155,  156,  157,  158,  159,  160,  978,  8242, 8804, 8260, 8734,  402,  9827, 9830,  9829, 9824,
    8596, 8592, 8593, 8594, 8595, 176,  177,  8243, 8805, 215,  8733, 8706, 8226, 247,   8800, 8801, 8776,  8230, 9116,
    9135, 8629, 8501, 8465, 8476, 8472, 8855, 8853, 8709, 8745, 8746, 8835, 8839, 8836,  8834, 8838, 8712,  8713, 8736,
    8711, 174,  169,  8482, 8719, 8730, 183,  172,  8743, 8744, 8660, 8656, 8657, 8658,  8659, 9674, 12296, 174,  169,
    8482, 8721, 9115, 9116, 9117, 9121, 9116, 9123, 9127, 9128, 9129, 9116, 240,  12297, 8747, 9127, 9116,  9133, 9131,
    9130, 9120, 9124, 9130, 9126, 9131, 9132, 9133, 255};

static double xfac[4] = {0, 0, -0.5, -1};

static double yfac[6] = {0, -1.2, -1, -0.5, 0, 0.2};

static int predef_font[] = {1, 1, 1, -2, -3, -4};

static int predef_prec[] = {0, 1, 2, 2, 2, 2};

static int predef_ints[] = {0, 1, 3, 3, 3};

static int predef_styli[] = {1, 1, 1, 2, 3};

static void set_clip_rect(int tnr);

template <typename Predicate, typename Iterator> class filter_iterator
{
public:
  using value_type = typename std::iterator_traits<Iterator>::value_type;
  using reference = typename std::iterator_traits<Iterator>::reference;
  using pointer = typename std::iterator_traits<Iterator>::pointer;
  using difference_type = typename std::iterator_traits<Iterator>::difference_type;

  filter_iterator(Predicate pred, Iterator iter, Iterator end) : pred_(pred), iter_(iter), end_(end)
  {
    advance_if_invalid();
  }

  reference operator*() const { return *iter_; }
  pointer operator->() const { return &(*iter_); }

  filter_iterator &operator++()
  {
    ++iter_;
    advance_if_invalid();
    return *this;
  }

  bool operator==(const filter_iterator &other) const { return iter_ == other.iter_; }
  bool operator!=(const filter_iterator &other) const { return !(*this == other); }

private:
  void advance_if_invalid()
  {
    while (iter_ != end_ && !pred_(*iter_))
      {
        ++iter_;
      }
  }

  Predicate pred_;
  Iterator iter_;
  Iterator end_;
};

class PartialPainters
{
public:
  struct PainterAndImage
  {
    bool active = true;
    std::unique_ptr<QImage> image; // image must come first, so the painter will be destroyed before the image
    std::unique_ptr<QPainter> painter;

    PainterAndImage(std::unique_ptr<QPainter> painter, std::unique_ptr<QImage> image)
        : painter(std::move(painter)), image(std::move(image))
    {
    }
  };

  struct PaintedRegion
  {
    unsigned int x, y, w, h; // position, width, height
    unsigned int *pixels;
  };

  using PartialPaintersIterator = filter_iterator<bool (*)(const std::pair<const unsigned int, PainterAndImage> &),
                                                  std::map<unsigned int, PainterAndImage>::iterator>;
  using PartialPaintersConstIterator = filter_iterator<bool (*)(const std::pair<const unsigned int, PainterAndImage> &),
                                                       std::map<unsigned int, PainterAndImage>::const_iterator>;

  explicit PartialPainters(const QPaintDevice &paint_device) : paint_device_(paint_device) {}

  void assign(const QPaintDevice &paint_device, bool keep_partial_paintings = false)
  {
    paint_device_ = paint_device;
    if (!keep_partial_paintings)
      {
        painters_and_images_.clear();
      }
  }

  void startPainting(unsigned int id)
  {
    {
      auto it = painters_and_images_.find(id);
      if (it != painters_and_images_.end())
        {
          it->second.active = true;
          return;
        }
    }
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
    auto image = std::unique_ptr<QImage>(
        new QImage(paint_device_.get().width() * paint_device_.get().devicePixelRatioF(),
                   paint_device_.get().height() * paint_device_.get().devicePixelRatioF(), QImage::Format_ARGB32));
    image->setDevicePixelRatio(paint_device_.get().devicePixelRatioF());
#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    auto image = std::unique_ptr<QImage>(
        new QImage(paint_device_.get().width() * paint_device_.get().devicePixelRatio(),
                   paint_device_.get().height() * paint_device_.get().devicePixelRatio(), QImage::Format_ARGB32));
    image->setDevicePixelRatio(paint_device_.get().devicePixelRatio());
#else
    auto image = std::unique_ptr<QImage>(
        new QImage(paint_device_.get().width(), paint_device_.get().height(), QImage::Format_ARGB32));
#endif
    image->fill(Qt::white);
    auto painter = std::unique_ptr<QPainter>(new QPainter(image.get()));
    painters_and_images_.emplace(id, PainterAndImage{std::move(painter), std::move(image)});
  };

  void stopPainting(unsigned int id) { painters_and_images_.at(id).active = false; }

  const unsigned int *getPixels(unsigned int id) const
  {
    return reinterpret_cast<const uint32_t *>(painters_and_images_.at(id).image->constBits());
  }

  std::map<unsigned int, const unsigned int *> getPixels() const
  {
    std::map<unsigned int, const unsigned int *> pixels;
    for (auto &painter_and_image : painters_and_images_)
      {
        pixels.emplace(painter_and_image.first,
                       reinterpret_cast<const uint32_t *>(painter_and_image.second.image->constBits()));
      }
    return pixels;
  }

  PaintedRegion getPaintedCRegion(unsigned int id) const
  {
    PaintedRegion region{UINT_MAX, UINT_MAX, 0, 0, nullptr};
    auto pixels = getPixels(id);
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
    unsigned int physical_width = paint_device_.get().width() * paint_device_.get().devicePixelRatioF();
    unsigned int physical_height = paint_device_.get().height() * paint_device_.get().devicePixelRatioF();
#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
    unsigned int physical_width = paint_device_.get().width() * paint_device_.get().devicePixelRatio();
    unsigned int physical_height = paint_device_.get().height() * paint_device_.get().devicePixelRatio();
#else
    unsigned int physical_width = paint_device_.get().width();
    unsigned int physical_height = paint_device_.get().height();
#endif

    for (unsigned int y = 0; y < physical_height; y++)
      {
        for (unsigned int x = 0; x < physical_width; x++)
          {
            // Check if the current pixels is not transparent
            if ((pixels[y * physical_width + x]) != 0xFFFFFFFF)
              {
                region.x = min(region.x, x);
                region.y = min(region.y, y);
                region.w = max(region.w, x);
                region.h = max(region.h, y);
              }
          }
      }
    region.w += 1 - region.x;
    region.h += 1 - region.y;

    region.pixels = reinterpret_cast<unsigned int *>(malloc(region.w * region.h * sizeof(unsigned int)));
    if (region.pixels == nullptr)
      {
#ifndef NO_EXCEPTIONS
        throw std::bad_alloc();
#else
        gks_perror("Couldn't allocate memory for partial mask");
        return region;
#endif
      }
    // Some elements don't have a visual representation such like an empty plot; to cover them this if is needed
    if (region.x == UINT_MAX || region.y == UINT_MAX) return region;
    // Strided memcpy (one memcpy invocation for every line of the found region)
    for (unsigned int y = 0; y < region.h; y++)
      {
        memcpy(region.pixels + y * region.w, pixels + (region.y + y) * physical_width + region.x,
               region.w * sizeof(unsigned int));
      }

    return region;
  }

  std::map<unsigned int, PaintedRegion> getPaintedCRegions() const
  {
    std::map<unsigned int, PaintedRegion> regions;
    for (auto &painter_and_image : painters_and_images_)
      {
        regions.emplace(painter_and_image.first, getPaintedCRegion(painter_and_image.first));
      }
    return regions;
  }

  PartialPaintersIterator begin()
  {
    return PartialPaintersIterator(isMapElementActive, painters_and_images_.begin(), painters_and_images_.end());
  }

  PartialPaintersIterator end()
  {
    return PartialPaintersIterator(isMapElementActive, painters_and_images_.end(), painters_and_images_.end());
  }

  PartialPaintersConstIterator begin() const
  {
    return PartialPaintersConstIterator(isMapElementActive, painters_and_images_.begin(), painters_and_images_.end());
  }

  PartialPaintersConstIterator end() const
  {
    return PartialPaintersConstIterator(isMapElementActive, painters_and_images_.end(), painters_and_images_.end());
  }

  PartialPaintersConstIterator cbegin() const noexcept
  {
    return PartialPaintersConstIterator(isMapElementActive, painters_and_images_.cbegin(), painters_and_images_.cend());
  }

  PartialPaintersConstIterator cend() const noexcept
  {
    return PartialPaintersConstIterator(isMapElementActive, painters_and_images_.cend(), painters_and_images_.cend());
  }

#define paint(method, ...)                                       \
  do                                                             \
    {                                                            \
      for (auto &painter_and_image : painters_and_images_)       \
        {                                                        \
          painter_and_image.second.painter->method(__VA_ARGS__); \
        }                                                        \
    }                                                            \
  while (false)

#define paintOnActive(method, ...)                               \
  do                                                             \
    {                                                            \
      for (auto &painter_and_image : *this)                      \
        {                                                        \
          painter_and_image.second.painter->method(__VA_ARGS__); \
        }                                                        \
    }                                                            \
  while (false)

  void setCompositionMode(QPainter::CompositionMode mode) { paint(setCompositionMode, mode); }

  void setFont(const QFont &f) { paint(setFont, f); }

  void setPen(const QColor &color) { paint(setPen, color); }
  void setPen(const QPen &pen) { paint(setPen, pen); }
  void setPen(Qt::PenStyle style) { paint(setPen, style); }

  void setBrush(const QBrush &brush) { paint(setBrush, brush); }
  void setBrush(Qt::BrushStyle style) { paint(setBrush, style); }
  void setBrush(QColor color) { paint(setBrush, color); }
  void setBrush(Qt::GlobalColor color) { paint(setBrush, color); }

  void setBackgroundMode(Qt::BGMode mode) { paint(setBackgroundMode, mode); }

  void setBrushOrigin(int x, int y) { paint(setBrushOrigin, x, y); }
  void setBrushOrigin(const QPoint &point) { paint(setBrushOrigin, point); }
  void setBrushOrigin(const QPointF &point) { paint(setBrushOrigin, point); }

  void setBackground(const QBrush &bg) { paint(setBackground, bg); }

  void setOpacity(qreal opacity) { paint(setOpacity, opacity); }

  void setClipRect(const QRectF &rect, Qt::ClipOperation op = Qt::ReplaceClip) { paint(setClipRect, rect, op); }
  void setClipRect(const QRect &rect, Qt::ClipOperation op = Qt::ReplaceClip) { paint(setClipRect, rect, op); }
  void setClipRect(int x, int y, int w, int h, Qt::ClipOperation op = Qt::ReplaceClip)
  {
    paint(setClipRect, x, y, w, h, op);
  }

  void setClipRegion(const QRegion &region, Qt::ClipOperation op = Qt::ReplaceClip)
  {
    paint(setClipRegion, region, op);
  }

  void setClipPath(const QPainterPath &path, Qt::ClipOperation op = Qt::ReplaceClip) { paint(setClipPath, path, op); }

  void setClipping(bool enable) { paint(setClipping, enable); }

  void save() { paint(save); }
  void restore() { paint(restore); }

  void setTransform(const QTransform &transform, bool combine = false) { paint(setTransform, transform, combine); }
  void resetTransform() { paint(resetTransform); }

  void setWorldTransform(const QTransform &matrix, bool combine = false) { paint(setWorldTransform, matrix, combine); }

  void setWorldMatrixEnabled(bool enabled) { paint(setWorldMatrixEnabled, enabled); }

  void scale(qreal sx, qreal sy) { paint(scale, sx, sy); }
  void shear(qreal sh, qreal sv) { paint(shear, sh, sv); }
  void rotate(qreal a) { paint(rotate, a); }

  void translate(const QPointF &offset) { paint(translate, offset); }
  void translate(const QPoint &offset) { paint(translate, offset); }
  void translate(qreal dx, qreal dy) { paint(translate, dx, dy); }

  void setWindow(const QRect &window) { paint(setWindow, window); }
  void setWindow(int x, int y, int w, int h) { paint(setWindow, x, y, w, h); }

  void setViewport(const QRect &viewport) { paint(setViewport, viewport); }
  void setViewport(int x, int y, int w, int h) { paint(setViewport, x, y, w, h); }

  void setViewTransformEnabled(bool enable) { paint(setViewTransformEnabled, enable); }

  void strokePath(const QPainterPath &path, const QPen &pen) { paintOnActive(strokePath, path, pen); }
  void fillPath(const QPainterPath &path, const QBrush &brush) { paintOnActive(fillPath, path, brush); }
  void drawPath(const QPainterPath &path) { paintOnActive(drawPath, path); }

  void drawPoint(const QPointF &pt) { paintOnActive(drawPoint, pt); }
  void drawPoint(const QPoint &point) { paintOnActive(drawPoint, point); }
  void drawPoint(int x, int y) { paintOnActive(drawPoint, x, y); }

  void drawPoints(const QPointF *points, int point_count) { paintOnActive(drawPoints, points, point_count); }
  void drawPoints(const QPolygonF &points) { paintOnActive(drawPoints, points); }
  void drawPoints(const QPoint *points, int point_count) { paintOnActive(drawPoints, points, point_count); }
  void drawPoints(const QPolygon &points) { paintOnActive(drawPoints, points); }

  void drawLine(const QLineF &line) { paintOnActive(drawLine, line); }
  void drawLine(const QLine &line) { paintOnActive(drawLine, line); }
  void drawLine(int x1, int y1, int x2, int y2) { paintOnActive(drawLine, x1, y1, x2, y2); }
  void drawLine(const QPoint &p1, const QPoint &p2) { paintOnActive(drawLine, p1, p2); }
  void drawLine(const QPointF &p1, const QPointF &p2) { paintOnActive(drawLine, p1, p2); }

  void drawLines(const QLineF *lines, int line_count) { paintOnActive(drawLines, lines, line_count); }
  void drawLines(const QPointF *point_pairs, int line_count) { paintOnActive(drawLines, point_pairs, line_count); }
  void drawLines(const QLine *lines, int line_count) { paintOnActive(drawLines, lines, line_count); }
  void drawLines(const QPoint *point_pairs, int line_count) { paintOnActive(drawLines, point_pairs, line_count); }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  void drawLines(const QList<QLineF> &lines) { paintOnActive(drawLines, lines); }
  void drawLines(const QList<QPointF> &point_pairs) { paintOnActive(drawLines, point_pairs); }
  void drawLines(const QList<QLine> &lines) { paintOnActive(drawLines, lines); }
  void drawLines(const QList<QPoint> &point_pairs) { paintOnActive(drawLines, point_pairs); }
#endif

  void drawRect(const QRectF &rect) { paintOnActive(drawRect, rect); }
  void drawRect(int x1, int y1, int w, int h) { paintOnActive(drawRect, x1, y1, w, h); }
  void drawRect(const QRect &rect) { paintOnActive(drawRect, rect); }

  void drawRects(const QRectF *rects, int rect_count) { paintOnActive(drawRects, rects, rect_count); }
  void drawRects(const QRect *rects, int rect_count) { paintOnActive(drawRects, rects, rect_count); }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  void drawRects(const QList<QRectF> &rectangles) { paintOnActive(drawRects, rectangles); }
  void drawRects(const QList<QRect> &rectangles) { paintOnActive(drawRects, rectangles); }
#endif

  void drawEllipse(const QRectF &r) { paintOnActive(drawEllipse, r); }
  void drawEllipse(const QRect &r) { paintOnActive(drawEllipse, r); }
  void drawEllipse(int x, int y, int w, int h) { paintOnActive(drawEllipse, x, y, w, h); }

  void drawEllipse(const QPointF &center, qreal rx, qreal ry) { paintOnActive(drawEllipse, center, rx, ry); }
  void drawEllipse(const QPoint &center, int rx, int ry) { paintOnActive(drawEllipse, center, rx, ry); }

  void drawPolyline(const QPointF *points, int point_count) { paintOnActive(drawPolyline, points, point_count); }
  void drawPolyline(const QPolygonF &polyline) { paintOnActive(drawPolyline, polyline); }
  void drawPolyline(const QPoint *points, int point_count) { paintOnActive(drawPolyline, points, point_count); }
  void drawPolyline(const QPolygon &polygon) { paintOnActive(drawPolyline, polygon); }

  void drawPolygon(const QPointF *points, int point_count, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paintOnActive(drawPolygon, points, point_count, fill_rule);
  }
  void drawPolygon(const QPolygonF &polygon, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paintOnActive(drawPolygon, polygon, fill_rule);
  }
  void drawPolygon(const QPoint *points, int point_count, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paintOnActive(drawPolygon, points, point_count, fill_rule);
  }
  void drawPolygon(const QPolygon &polygon, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paintOnActive(drawPolygon, polygon, fill_rule);
  }

  void drawConvexPolygon(const QPointF *points, int point_count)
  {
    paintOnActive(drawConvexPolygon, points, point_count);
  }
  void drawConvexPolygon(const QPolygonF &polygon) { paintOnActive(drawConvexPolygon, polygon); }
  void drawConvexPolygon(const QPoint *points, int point_count)
  {
    paintOnActive(drawConvexPolygon, points, point_count);
  }
  void drawConvexPolygon(const QPolygon &polygon) { paintOnActive(drawConvexPolygon, polygon); }

  void drawArc(const QRectF &rect, int a, int alen) { paintOnActive(drawArc, rect, a, alen); }
  void drawArc(const QRect &rect, int a, int alen) { paintOnActive(drawArc, rect, a, alen); }
  void drawArc(int x, int y, int w, int h, int a, int alen) { paintOnActive(drawArc, x, y, w, h, a, alen); }

  void drawPie(const QRectF &rect, int a, int alen) { paintOnActive(drawPie, rect, a, alen); }
  void drawPie(int x, int y, int w, int h, int a, int alen) { paintOnActive(drawPie, x, y, w, h, a, alen); }
  void drawPie(const QRect &rect, int a, int alen) { paintOnActive(drawPie, rect, a, alen); }

  void drawChord(const QRectF &rect, int a, int alen) { paintOnActive(drawChord, rect, a, alen); }
  void drawChord(int x, int y, int w, int h, int a, int alen) { paintOnActive(drawChord, x, y, w, h, a, alen); }
  void drawChord(const QRect &rect, int a, int alen) { paintOnActive(drawChord, rect, a, alen); }

  void drawRoundedRect(const QRectF &rect, qreal x_radius, qreal y_radius, Qt::SizeMode mode = Qt::AbsoluteSize)
  {
    paintOnActive(drawRoundedRect, rect, x_radius, y_radius, mode);
  }
  void drawRoundedRect(int x, int y, int w, int h, qreal x_radius, qreal y_radius, Qt::SizeMode mode = Qt::AbsoluteSize)
  {
    paintOnActive(drawRoundedRect, x, y, w, h, x_radius, y_radius, mode);
  }
  void drawRoundedRect(const QRect &rect, qreal x_radius, qreal y_radius, Qt::SizeMode mode = Qt::AbsoluteSize)
  {
    paintOnActive(drawRoundedRect, rect, x_radius, y_radius, mode);
  }

  void drawTiledPixmap(const QRectF &rect, const QPixmap &pm, const QPointF &offset = QPointF())
  {
    paintOnActive(drawTiledPixmap, rect, pm, offset);
  }
  void drawTiledPixmap(int x, int y, int w, int h, const QPixmap &pixmap, int sx = 0, int sy = 0)
  {
    paintOnActive(drawTiledPixmap, x, y, w, h, pixmap, sx, sy);
  }
  void drawTiledPixmap(const QRect &rect, const QPixmap &pixmap, const QPoint &offset = QPoint())
  {
    paintOnActive(drawTiledPixmap, rect, pixmap, offset);
  }
  void drawPicture(const QPointF &point, const QPicture &picture) { paintOnActive(drawPicture, point, picture); }
  void drawPicture(int x, int y, const QPicture &picture) { paintOnActive(drawPicture, x, y, picture); }
  void drawPicture(const QPoint &point, const QPicture &picture) { paintOnActive(drawPicture, point, picture); }

  void drawPixmap(const QRectF &target_rect, const QPixmap &pixmap, const QRectF &source_rect)
  {
    paintOnActive(drawPixmap, target_rect, pixmap, source_rect);
  }
  void drawPixmap(const QRect &target_rect, const QPixmap &pixmap, const QRect &source_rect)
  {
    paintOnActive(drawPixmap, target_rect, pixmap, source_rect);
  }
  void drawPixmap(int x, int y, int w, int h, const QPixmap &pm, int sx, int sy, int sw, int sh)
  {
    paintOnActive(drawPixmap, x, y, w, h, pm, sx, sy, sw, sh);
  }
  void drawPixmap(int x, int y, const QPixmap &pm, int sx, int sy, int sw, int sh)
  {
    paintOnActive(drawPixmap, x, y, pm, sx, sy, sw, sh);
  }
  void drawPixmap(const QPointF &point, const QPixmap &pm, const QRectF &sr)
  {
    paintOnActive(drawPixmap, point, pm, sr);
  }
  void drawPixmap(const QPoint &point, const QPixmap &pm, const QRect &sr) { paintOnActive(drawPixmap, point, pm, sr); }
  void drawPixmap(const QPointF &point, const QPixmap &pm) { paintOnActive(drawPixmap, point, pm); }
  void drawPixmap(const QPoint &point, const QPixmap &pm) { paintOnActive(drawPixmap, point, pm); }
  void drawPixmap(int x, int y, const QPixmap &pm) { paintOnActive(drawPixmap, x, y, pm); }
  void drawPixmap(const QRect &r, const QPixmap &pm) { paintOnActive(drawPixmap, r, pm); }
  void drawPixmap(int x, int y, int w, int h, const QPixmap &pm) { paintOnActive(drawPixmap, x, y, w, h, pm); }

  void drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragment_count, const QPixmap &pixmap,
                           QPainter::PixmapFragmentHints hints = QPainter::PixmapFragmentHints())
  {
    paintOnActive(drawPixmapFragments, fragments, fragment_count, pixmap, hints);
  }

  void drawImage(const QRectF &target_rect, const QImage &image, const QRectF &source_rect,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    paintOnActive(drawImage, target_rect, image, source_rect, flags);
  }
  void drawImage(const QRect &target_rect, const QImage &image, const QRect &source_rect,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    paintOnActive(drawImage, target_rect, image, source_rect, flags);
  }
  void drawImage(const QPointF &point, const QImage &image, const QRectF &sr,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    paintOnActive(drawImage, point, image, sr, flags);
  }
  void drawImage(const QPoint &point, const QImage &image, const QRect &sr,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    paintOnActive(drawImage, point, image, sr, flags);
  }
  void drawImage(const QRectF &r, const QImage &image) { paintOnActive(drawImage, r, image); }
  void drawImage(const QRect &r, const QImage &image) { paintOnActive(drawImage, r, image); }
  void drawImage(const QPointF &point, const QImage &image) { paintOnActive(drawImage, point, image); }
  void drawImage(const QPoint &point, const QImage &image) { paintOnActive(drawImage, point, image); }
  void drawImage(int x, int y, const QImage &image, int sx = 0, int sy = 0, int sw = -1, int sh = -1,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    paintOnActive(drawImage, x, y, image, sx, sy, sw, sh, flags);
  }

  void setLayoutDirection(Qt::LayoutDirection direction) { paint(setLayoutDirection, direction); }

  void drawGlyphRun(const QPointF &position, const QGlyphRun &glyph_run)
  {
    paintOnActive(drawGlyphRun, position, glyph_run);
  }

  void drawStaticText(const QPointF &top_left_position, const QStaticText &static_text)
  {
    paintOnActive(drawStaticText, top_left_position, static_text);
  }
  void drawStaticText(const QPoint &top_left_position, const QStaticText &static_text)
  {
    paintOnActive(drawStaticText, top_left_position, static_text);
  }
  void drawStaticText(int left, int top, const QStaticText &static_text)
  {
    paintOnActive(drawStaticText, left, top, static_text);
  }

  void drawText(const QPointF &point, const QString &s) { paintOnActive(drawText, point, s); }
  void drawText(const QPoint &point, const QString &s) { paintOnActive(drawText, point, s); }
  void drawText(int x, int y, const QString &s) { paintOnActive(drawText, x, y, s); }

  void drawText(const QPointF &point, const QString &str, int tf, int justification_padding)
  {
    paintOnActive(drawText, point, str, tf, justification_padding);
  }

  void drawText(const QRectF &r, int flags, const QString &text, QRectF *br = nullptr)
  {
    paintOnActive(drawText, r, flags, text, br);
  }
  void drawText(const QRect &r, int flags, const QString &text, QRect *br = nullptr)
  {
    paintOnActive(drawText, r, flags, text, br);
  }
  void drawText(int x, int y, int w, int h, int flags, const QString &text, QRect *br = nullptr)
  {
    paintOnActive(drawText, x, y, w, h, flags, text, br);
  }

  void drawText(const QRectF &r, const QString &text, const QTextOption &o = QTextOption())
  {
    paintOnActive(drawText, r, text, o);
  }

  void drawTextItem(const QPointF &point, const QTextItem &ti) { paintOnActive(drawTextItem, point, ti); }
  void drawTextItem(int x, int y, const QTextItem &ti) { paintOnActive(drawTextItem, x, y, ti); }
  void drawTextItem(const QPoint &point, const QTextItem &ti) { paintOnActive(drawTextItem, point, ti); }

  void fillRect(const QRectF &rect, const QBrush &brush) { paintOnActive(fillRect, rect, brush); }
  void fillRect(int x, int y, int w, int h, const QBrush &brush) { paintOnActive(fillRect, x, y, w, h, brush); }
  void fillRect(const QRect &rect, const QBrush &brush) { paintOnActive(fillRect, rect, brush); }

  void fillRect(const QRectF &rect, const QColor &color) { paintOnActive(fillRect, rect, color); }
  void fillRect(int x, int y, int w, int h, const QColor &color) { paintOnActive(fillRect, x, y, w, h, color); }
  void fillRect(const QRect &rect, const QColor &color) { paintOnActive(fillRect, rect, color); }

  void fillRect(int x, int y, int w, int h, Qt::GlobalColor c) { paintOnActive(fillRect, x, y, w, h, c); }
  void fillRect(const QRect &r, Qt::GlobalColor c) { paintOnActive(fillRect, r, c); }
  void fillRect(const QRectF &r, Qt::GlobalColor c) { paintOnActive(fillRect, r, c); }

  void fillRect(int x, int y, int w, int h, Qt::BrushStyle style) { paintOnActive(fillRect, x, y, w, h, style); }
  void fillRect(const QRect &r, Qt::BrushStyle style) { paintOnActive(fillRect, r, style); }
  void fillRect(const QRectF &r, Qt::BrushStyle style) { paintOnActive(fillRect, r, style); }

#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
  void fillRect(int x, int y, int w, int h, QGradient::Preset preset) { paintOnActive(fillRect, x, y, w, h, preset); }
  void fillRect(const QRect &r, QGradient::Preset preset) { paintOnActive(fillRect, r, preset); }
  void fillRect(const QRectF &r, QGradient::Preset preset) { paintOnActive(fillRect, r, preset); }
#endif

  void eraseRect(const QRectF &rect) { paintOnActive(eraseRect, rect); }
  void eraseRect(int x, int y, int w, int h) { paintOnActive(eraseRect, x, y, w, h); }
  void eraseRect(const QRect &rect) { paintOnActive(eraseRect, rect); }

  void setRenderHint(QPainter::RenderHint hint, bool on = true) { paint(setRenderHint, hint, on); }
  void setRenderHints(QPainter::RenderHints hints, bool on = true) { paint(setRenderHints, hints, on); }

  void beginNativePainting() { paint(beginNativePainting); }
  void endNativePainting() { paint(endNativePainting); }

#undef paint
#undef paintOnActive

private:
  static bool isMapElementActive(const std::pair<const unsigned int, PainterAndImage> &id_to_painter_and_image)
  {
    return id_to_painter_and_image.second.active;
  }

  std::reference_wrapper<const QPaintDevice> paint_device_;
  std::map<unsigned int, PainterAndImage> painters_and_images_;
};


class GroupMask
{
public:
  // Use RGB32 instead of RGB888 to increase performance.
  GroupMask(int width, int height) { resize(width, height); }

  void resize(int width, int height)
  {
    if (width_ == width && height_ == height) return;
    // A Painter which is assigned to a QPaintDevice must be destroyed before the QPaintDevice is destroyed.
    mask_painter_.reset();
    mask_image_.reset();
    mask_image_ = std::unique_ptr<QImage>(new QImage(width, height, QImage::Format_RGB32));
    mask_painter_ = std::unique_ptr<QPainter>(new QPainter(mask_image_.get()));
    configureImageAndPainter();
    width_ = width;
    height_ = height;
  }

  void clear() { mask_image_->fill(Qt::white); }

  void clearBackground() { background_.reset(); }

  void drawBackground()
  {
    if (background_)
      {
        if (gkss->cntnr != 0) set_clip_rect(0);
        mask_painter_->drawImage(0, 0, *background_);
        if (gkss->cntnr != 0) set_clip_rect(gkss->cntnr);
      }
  }

  void freezeBackground(double xmin, double xmax, double ymin, double ymax)
  {
    background_ = std::unique_ptr<QImage>(new QImage(*mask_image_.get()));
    {
      QPainter backgroundPainter(background_.get());
      backgroundPainter.fillRect(QRectF(xmin, ymin, xmax - xmin, ymax - ymin), Qt::white);
    }
  }

  bool hasBackground() const { return static_cast<bool>(background_); }

  void id(unsigned int id)
  {
    auto pen = mask_painter_->pen();
    pen.setColor(QColor::fromRgb(id));
    mask_painter_->setPen(pen);
    auto brush = mask_painter_->brush();
    brush.setColor(QColor::fromRgb(id));
    mask_painter_->setBrush(brush);
  }

  unsigned int id() const { return mask_painter_->pen().color().rgb() & 0x00FFFFFF; }

  const unsigned int *pixels() const { return reinterpret_cast<const uint32_t *>(this->mask_image_->constBits()); }

  unsigned int operator()(unsigned int x, unsigned int y) const
  {
    // Use `& 0x00FFFFFF` to remove the alpha channel which is always `0xFF` because of the RGB32 format.
    return pixels()[y * mask_image_->width() + x] & 0x00FFFFFF;
  }

  bool hasPixel(unsigned int x, unsigned int y) const { return this->operator()(x, y) != 0; }

  QPainter &painter() { return *mask_painter_.get(); }
  const QPainter &painter() const { return *mask_painter_.get(); }

  QImage &image() { return *mask_image_.get(); }
  const QImage &image() const { return *mask_image_.get(); }

  unsigned int width() const { return mask_image_->width(); }

  unsigned int height() const { return mask_image_->height(); }

  unsigned int *toCMask() const
  {
    unsigned int *cmask =
        reinterpret_cast<unsigned int *>(malloc(mask_image_->width() * mask_image_->height() * sizeof(unsigned int)));
    if (cmask == nullptr)
      {
#ifndef NO_EXCEPTIONS
        throw std::bad_alloc();
#else
        return nullptr;
#endif
      }
    memcpy(cmask, pixels(), mask_image_->width() * mask_image_->height() * sizeof(unsigned int));
    return cmask;
  }

protected:
  void configureImageAndPainter()
  {
    // `white` or `0xFFFFFFFF` is no valid ID.
    mask_image_->fill(Qt::white);
    mask_painter_->setRenderHint(QPainter::Antialiasing, false);
    mask_painter_->setRenderHint(QPainter::TextAntialiasing, false);
    mask_painter_->setCompositionMode(QPainter::CompositionMode_Source);
    auto font = mask_painter_->font();
    font.setStyleStrategy(QFont::NoAntialias);
    mask_painter_->setFont(font);
  }

private:
  int width_ = -1, height_ = -1;
  std::unique_ptr<QImage> mask_image_;
  std::unique_ptr<QPainter> mask_painter_;
  std::unique_ptr<QImage> background_;
};

class ProxyPainter
{
public:
  ProxyPainter(QPainter &painter, const QPaintDevice &paint_device) : painter_(painter), partial_painters_(paint_device)
  {
  }

  ProxyPainter(QPixmap &pixmap, const QPaintDevice &paint_device)
      : owned_painter_(new QPainter(&pixmap)), painter_(*owned_painter_), partial_painters_(paint_device)
  {
  }

  void assign(QPainter &painter, const QPaintDevice &paint_device)
  {
    painter_ = painter;
    if (group_mask_)
      {
        group_mask_->resize(paint_device.width(), paint_device.height());
        if (!group_stack_.empty()) mask_painter_ = &group_mask_->painter();
      }
    partial_painters_.assign(paint_device);
  }

  void assign(QPixmap &pixmap, const QPaintDevice &paint_device)
  {
    owned_painter_.reset();
    owned_painter_ = std::unique_ptr<QPainter>(new QPainter(&pixmap));
    assign(*owned_painter_, paint_device);
  }

  void clear()
  {
    painter_.get().fillRect(QRect(0, 0, painter_.get().device()->width(), painter_.get().device()->height()),
                            Qt::white);
    if (group_mask_) group_mask_->clear();
  }

  void clearBackground()
  {
    background_.reset();
    if (group_mask_) group_mask_->clearBackground();
  }

  void drawBackground()
  {
    if (background_)
      {
        if (gkss->cntnr != 0) set_clip_rect(0);
        painter_.get().drawPixmap(0, 0, *background_);
        if (gkss->cntnr != 0) set_clip_rect(gkss->cntnr);
        if (group_mask_) group_mask_->drawBackground();
      }
  }

  void freezeBackground(double xmin, double xmax, double ymin, double ymax)
  {
    // Assume that the painter's device is a QPixmap (this is true for all applications which buffer drawing
    // operations onto a pixmap which should always be done).
    const auto *current_pixmap = dynamic_cast<QPixmap *>(painter_.get().device());
    if (current_pixmap == NULL) return;
    background_ = std::unique_ptr<QPixmap>(new QPixmap(*current_pixmap));
    {
      QPainter backgroundPainter(background_.get());
      backgroundPainter.fillRect(QRectF(xmin, ymin, xmax - xmin, ymax - ymin), Qt::white);
    }
    if (group_mask_) group_mask_->freezeBackground(xmin, xmax, ymin, ymax);
  }

  bool hasBackground() const { return static_cast<bool>(background_); }

  void beginGroup(unsigned int id)
  {
    if (!group_mask_)
      {
        group_mask_ = std::unique_ptr<GroupMask>(new GroupMask(
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
            painter_.get().device()->width() / painter_.get().device()->devicePixelRatioF(),
            painter_.get().device()->height() / painter_.get().device()->devicePixelRatioF()
#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
            painter_.get().device()->width() / painter_.get().device()->devicePixelRatio(),
            painter_.get().device()->height() / painter_.get().device()->devicePixelRatio()
#else
            painter_.get().device()->width(), painter_.get().device()->height()
#endif
                ));
      }
    if (group_stack_.empty()) mask_painter_ = &group_mask_->painter();
    group_mask_->id(id);
    group_stack_.push(id);
  }

  void endGroup()
  {
    group_stack_.pop();
    // 0x00FFFFFF (RGB white) is no valid ID
    group_mask_->id(group_stack_.empty() ? 0x00FFFFFF : group_stack_.top());
    if (group_stack_.empty()) mask_painter_ = nullptr;
  }

  bool hasOpenGroup() const { return !group_stack_.empty(); }

  const GroupMask *groupMask() const { return group_mask_.get(); }

  void beginPartial(unsigned int id) { partial_painters_.startPainting(id); }
  void endPartial(unsigned int id) { partial_painters_.stopPainting(id); }

  std::map<unsigned int, PartialPainters::PaintedRegion> partialCRegions() const
  {
    return partial_painters_.getPaintedCRegions();
  }

  // Wrapped QPainter methods start here:
#define paint(method, ...)                                                  \
  do                                                                        \
    {                                                                       \
      partial_painters_.method(__VA_ARGS__);                                \
      if (!p->partial_draw)                                                 \
        {                                                                   \
          if (mask_painter_ != nullptr) mask_painter_->method(__VA_ARGS__); \
          painter_.get().method(__VA_ARGS__);                               \
        }                                                                   \
    }                                                                       \
  while (0)
#define paint_and_return(method, ...)                                   \
  do                                                                    \
    {                                                                   \
      if (mask_painter_ != nullptr) mask_painter_->method(__VA_ARGS__); \
      return painter_.get().method(__VA_ARGS__);                        \
    }                                                                   \
  while (0)


  bool begin(QPaintDevice *paint_device) { paint_and_return(begin, paint_device); }
  bool end() { paint_and_return(end); }
  bool isActive() const { return painter_.get().isActive(); }
  QPainter::CompositionMode compositionMode() { return painter_.get().compositionMode(); }
  void setCompositionMode(QPainter::CompositionMode mode)
  {
    if (!p->partial_draw)
      {
        painter_.get().setCompositionMode(mode);
      }
    partial_painters_.setCompositionMode(mode);
  }

  void setFont(const QFont &f) { paint(setFont, f); }

  QFontMetrics fontMetrics() const { return painter_.get().fontMetrics(); }
  QFontInfo fontInfo() const { return painter_.get().fontInfo(); }

  const QPen &pen() const { return painter_.get().pen(); }
  void setPen(const QColor &color)
  {
    partial_painters_.setPen(color);
    if (!p->partial_draw)
      {
        painter_.get().setPen(color);
        applyPenToMask();
      }
  }
  void setPen(const QPen &pen)
  {
    partial_painters_.setPen(pen);
    if (!p->partial_draw)
      {
        painter_.get().setPen(pen);
        applyPenToMask();
      }
  }
  void setPen(Qt::PenStyle style)
  {
    partial_painters_.setPen(style);
    if (!p->partial_draw)
      {
        painter_.get().setPen(style);
        applyPenToMask();
      }
  }

  const QBrush &brush() const { return painter_.get().brush(); }
  void setBrush(const QBrush &brush)
  {
    partial_painters_.setBrush(brush);
    if (!p->partial_draw)
      {
        painter_.get().setBrush(brush);
        applyBrushToMask();
      }
  }
  void setBrush(Qt::BrushStyle style)
  {
    partial_painters_.setBrush(style);
    if (!p->partial_draw)
      {
        painter_.get().setBrush(style);
        applyBrushToMask();
      }
  }
  void setBrush(QColor color)
  {
    partial_painters_.setBrush(color);
    if (!p->partial_draw)
      {
        painter_.get().setBrush(color);
        applyBrushToMask();
      }
  }
  void setBrush(Qt::GlobalColor color)
  {
    partial_painters_.setBrush(color);
    if (!p->partial_draw)
      {
        painter_.get().setBrush(color);
        applyBrushToMask();
      }
  }

  Qt::BGMode backgroundMode() const { return painter_.get().backgroundMode(); }
  void setBackgroundMode(Qt::BGMode mode) { paint(setBackgroundMode, mode); }

  QPoint brushOrigin() const { return painter_.get().brushOrigin(); }
  void setBrushOrigin(int x, int y) { paint(setBrushOrigin, x, y); }
  void setBrushOrigin(const QPoint &point) { paint(setBrushOrigin, point); }
  void setBrushOrigin(const QPointF &point) { paint(setBrushOrigin, point); }

  const QBrush &background() const { return painter_.get().background(); }
  void setBackground(const QBrush &bg)
  {
    partial_painters_.setBackground(bg);
    if (!p->partial_draw)
      {
        painter_.get().setBackground(bg);
        if (mask_painter_ != nullptr) mask_painter_->setBackground(createMaskBrush(bg));
      }
  }

  qreal opacity() const { return painter_.get().opacity(); }
  void setOpacity(qreal opacity) { paint(setOpacity, opacity); }

  void setClipRect(const QRectF &rect, Qt::ClipOperation op = Qt::ReplaceClip) { paint(setClipRect, rect, op); }
  void setClipRect(const QRect &rect, Qt::ClipOperation op = Qt::ReplaceClip) { paint(setClipRect, rect, op); }
  void setClipRect(int x, int y, int w, int h, Qt::ClipOperation op = Qt::ReplaceClip)
  {
    paint(setClipRect, x, y, w, h, op);
  }

  QRegion clipRegion() const { return painter_.get().clipRegion(); }
  void setClipRegion(const QRegion &region, Qt::ClipOperation op = Qt::ReplaceClip)
  {
    paint(setClipRegion, region, op);
  }

  QPainterPath clipPath() const { return painter_.get().clipPath(); }
  void setClipPath(const QPainterPath &path, Qt::ClipOperation op = Qt::ReplaceClip) { paint(setClipPath, path, op); }

  bool hasClipping() const { return painter_.get().hasClipping(); }
  void setClipping(bool enable) { paint(setClipping, enable); }
  QRectF clipBoundingRect() const { return painter_.get().clipBoundingRect(); }

  void save() { paint(save); }
  void restore() { paint(restore); }

  const QTransform &transform() const { return painter_.get().transform(); }
  void setTransform(const QTransform &transform, bool combine = false) { paint(setTransform, transform, combine); }
  void resetTransform() { paint(resetTransform); }

  const QTransform &deviceTransform() const { return painter_.get().deviceTransform(); }
  const QTransform &worldTransform() const { return painter_.get().worldTransform(); }
  void setWorldTransform(const QTransform &matrix, bool combine = false) { paint(setWorldTransform, matrix, combine); }

  QTransform combinedTransform() const { return painter_.get().combinedTransform(); }

  bool worldMatrixEnabled() const { return painter_.get().worldMatrixEnabled(); }
  void setWorldMatrixEnabled(bool enabled) { paint(setWorldMatrixEnabled, enabled); }

  void scale(qreal sx, qreal sy) { paint(scale, sx, sy); }
  void shear(qreal sh, qreal sv) { paint(shear, sh, sv); }
  void rotate(qreal a) { paint(rotate, a); }

  void translate(const QPointF &offset) { paint(translate, offset); }
  void translate(const QPoint &offset) { paint(translate, offset); }
  void translate(qreal dx, qreal dy) { paint(translate, dx, dy); }

  QRect window() const { return painter_.get().window(); }
  void setWindow(const QRect &window) { paint(setWindow, window); }
  void setWindow(int x, int y, int w, int h) { paint(setWindow, x, y, w, h); }

  QRect viewport() const { return painter_.get().viewport(); }
  void setViewport(const QRect &viewport) { paint(setViewport, viewport); }
  void setViewport(int x, int y, int w, int h) { paint(setViewport, x, y, w, h); }

  bool viewTransformEnabled() const { return painter_.get().viewTransformEnabled(); }
  void setViewTransformEnabled(bool enable) { paint(setViewTransformEnabled, enable); }

  void strokePath(const QPainterPath &path, const QPen &pen)
  {
    partial_painters_.strokePath(path, pen);
    if (!p->partial_draw)
      {
        painter_.get().strokePath(path, pen);
        if (mask_painter_ != nullptr) mask_painter_->strokePath(path, createMaskPen(pen));
      }
  }
  void fillPath(const QPainterPath &path, const QBrush &brush)
  {
    partial_painters_.fillPath(path, brush);
    if (!p->partial_draw)
      {
        painter_.get().fillPath(path, brush);
        if (mask_painter_ != nullptr) mask_painter_->fillPath(path, createMaskBrush(brush));
      }
  }
  void drawPath(const QPainterPath &path) { paint(drawPath, path); }

  void drawPoint(const QPointF &pt) { paint(drawPoint, pt); }
  void drawPoint(const QPoint &point) { paint(drawPoint, point); }
  void drawPoint(int x, int y) { paint(drawPoint, x, y); }

  void drawPoints(const QPointF *points, int point_count) { paint(drawPoints, points, point_count); }
  void drawPoints(const QPolygonF &points) { paint(drawPoints, points); }
  void drawPoints(const QPoint *points, int point_count) { paint(drawPoints, points, point_count); }
  void drawPoints(const QPolygon &points) { paint(drawPoints, points); }

  void drawLine(const QLineF &line) { paint(drawLine, line); }
  void drawLine(const QLine &line) { paint(drawLine, line); }
  void drawLine(int x1, int y1, int x2, int y2) { paint(drawLine, x1, y1, x2, y2); }
  void drawLine(const QPoint &p1, const QPoint &p2) { paint(drawLine, p1, p2); }
  void drawLine(const QPointF &p1, const QPointF &p2) { paint(drawLine, p1, p2); }

  void drawLines(const QLineF *lines, int line_count) { paint(drawLines, lines, line_count); }
  void drawLines(const QPointF *point_pairs, int line_count) { paint(drawLines, point_pairs, line_count); }
  void drawLines(const QLine *lines, int line_count) { paint(drawLines, lines, line_count); }
  void drawLines(const QPoint *point_pairs, int line_count) { paint(drawLines, point_pairs, line_count); }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  void drawLines(const QList<QLineF> &lines) { paint(drawLines, lines); }
  void drawLines(const QList<QPointF> &point_pairs) { paint(drawLines, point_pairs); }
  void drawLines(const QList<QLine> &lines) { paint(drawLines, lines); }
  void drawLines(const QList<QPoint> &point_pairs) { paint(drawLines, point_pairs); }
#endif

  void drawRect(const QRectF &rect) { paint(drawRect, rect); }
  void drawRect(int x1, int y1, int w, int h) { paint(drawRect, x1, y1, w, h); }
  void drawRect(const QRect &rect) { paint(drawRect, rect); }

  void drawRects(const QRectF *rects, int rect_count) { paint(drawRects, rects, rect_count); }
  void drawRects(const QRect *rects, int rect_count) { paint(drawRects, rects, rect_count); }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
  void drawRects(const QList<QRectF> &rectangles) { paint(drawRects, rectangles); }
  void drawRects(const QList<QRect> &rectangles) { paint(drawRects, rectangles); }
#endif

  void drawEllipse(const QRectF &r) { paint(drawEllipse, r); }
  void drawEllipse(const QRect &r) { paint(drawEllipse, r); }
  void drawEllipse(int x, int y, int w, int h) { paint(drawEllipse, x, y, w, h); }

  void drawEllipse(const QPointF &center, qreal rx, qreal ry) { paint(drawEllipse, center, rx, ry); }
  void drawEllipse(const QPoint &center, int rx, int ry) { paint(drawEllipse, center, rx, ry); }

  void drawPolyline(const QPointF *points, int point_count) { paint(drawPolyline, points, point_count); }
  void drawPolyline(const QPolygonF &polyline) { paint(drawPolyline, polyline); }
  void drawPolyline(const QPoint *points, int point_count) { paint(drawPolyline, points, point_count); }
  void drawPolyline(const QPolygon &polygon) { paint(drawPolyline, polygon); }

  void drawPolygon(const QPointF *points, int point_count, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paint(drawPolygon, points, point_count, fill_rule);
  }
  void drawPolygon(const QPolygonF &polygon, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paint(drawPolygon, polygon, fill_rule);
  }
  void drawPolygon(const QPoint *points, int point_count, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paint(drawPolygon, points, point_count, fill_rule);
  }
  void drawPolygon(const QPolygon &polygon, Qt::FillRule fill_rule = Qt::OddEvenFill)
  {
    paint(drawPolygon, polygon, fill_rule);
  }

  void drawConvexPolygon(const QPointF *points, int point_count) { paint(drawConvexPolygon, points, point_count); }
  void drawConvexPolygon(const QPolygonF &polygon) { paint(drawConvexPolygon, polygon); }
  void drawConvexPolygon(const QPoint *points, int point_count) { paint(drawConvexPolygon, points, point_count); }
  void drawConvexPolygon(const QPolygon &polygon) { paint(drawConvexPolygon, polygon); }

  void drawArc(const QRectF &rect, int a, int alen) { paint(drawArc, rect, a, alen); }
  void drawArc(const QRect &rect, int a, int alen) { paint(drawArc, rect, a, alen); }
  void drawArc(int x, int y, int w, int h, int a, int alen) { paint(drawArc, x, y, w, h, a, alen); }

  void drawPie(const QRectF &rect, int a, int alen) { paint(drawPie, rect, a, alen); }
  void drawPie(int x, int y, int w, int h, int a, int alen) { paint(drawPie, x, y, w, h, a, alen); }
  void drawPie(const QRect &rect, int a, int alen) { paint(drawPie, rect, a, alen); }

  void drawChord(const QRectF &rect, int a, int alen) { paint(drawChord, rect, a, alen); }
  void drawChord(int x, int y, int w, int h, int a, int alen) { paint(drawChord, x, y, w, h, a, alen); }
  void drawChord(const QRect &rect, int a, int alen) { paint(drawChord, rect, a, alen); }

  void drawRoundedRect(const QRectF &rect, qreal x_radius, qreal y_radius, Qt::SizeMode mode = Qt::AbsoluteSize)
  {
    paint(drawRoundedRect, rect, x_radius, y_radius, mode);
  }
  void drawRoundedRect(int x, int y, int w, int h, qreal x_radius, qreal y_radius, Qt::SizeMode mode = Qt::AbsoluteSize)
  {
    paint(drawRoundedRect, x, y, w, h, x_radius, y_radius, mode);
  }
  void drawRoundedRect(const QRect &rect, qreal x_radius, qreal y_radius, Qt::SizeMode mode = Qt::AbsoluteSize)
  {
    paint(drawRoundedRect, rect, x_radius, y_radius, mode);
  }

  void drawTiledPixmap(const QRectF &rect, const QPixmap &pm, const QPointF &offset = QPointF())
  {
    partial_painters_.drawTiledPixmap(rect, pm, offset);
    if (!p->partial_draw)
      {
        painter_.get().drawTiledPixmap(rect, pm, offset);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(rect, mask_painter_->brush().color());
      }
  }
  void drawTiledPixmap(int x, int y, int w, int h, const QPixmap &pixmap, int sx = 0, int sy = 0)
  {
    partial_painters_.drawTiledPixmap(x, y, w, h, pixmap, sx, sy);
    if (!p->partial_draw)
      {
        painter_.get().drawTiledPixmap(x, y, w, h, pixmap, sx, sy);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(x, y, w, h, mask_painter_->brush().color());
      }
  }
  void drawTiledPixmap(const QRect &rect, const QPixmap &pixmap, const QPoint &offset = QPoint())
  {
    partial_painters_.drawTiledPixmap(rect, pixmap, offset);
    if (!p->partial_draw)
      {
        painter_.get().drawTiledPixmap(rect, pixmap, offset);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(rect, mask_painter_->brush().color());
      }
  }
  void drawPicture(const QPointF &point, const QPicture &picture)
  {
    partial_painters_.drawPicture(point, picture);
    if (!p->partial_draw)
      {
        painter_.get().drawPicture(point, picture);
        if (mask_painter_ != nullptr)
          mask_painter_->fillRect(QRectF(point, picture.boundingRect().size()), mask_painter_->brush().color());
      }
  }
  void drawPicture(int x, int y, const QPicture &picture)
  {
    partial_painters_.drawPicture(x, y, picture);
    if (!p->partial_draw)
      {
        painter_.get().drawPicture(x, y, picture);
        if (mask_painter_ != nullptr)
          mask_painter_->fillRect(QRect(QPoint(x, y), picture.boundingRect().size()), mask_painter_->brush().color());
      }
  }
  void drawPicture(const QPoint &point, const QPicture &picture)
  {
    partial_painters_.drawPicture(point, picture);
    if (!p->partial_draw)
      {
        painter_.get().drawPicture(point, picture);
        if (mask_painter_ != nullptr)
          mask_painter_->fillRect(QRect(point, picture.boundingRect().size()), mask_painter_->brush().color());
      }
  }

  void drawPixmap(const QRectF &target_rect, const QPixmap &pixmap, const QRectF &source_rect)
  {
    partial_painters_.drawPixmap(target_rect, pixmap, source_rect);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(target_rect, pixmap, source_rect);
        if (mask_painter_ != nullptr) drawMaskForPixmap(target_rect, pixmap, source_rect);
      }
  }
  void drawPixmap(const QRect &target_rect, const QPixmap &pixmap, const QRect &source_rect)
  {
    partial_painters_.drawPixmap(target_rect, pixmap, source_rect);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(target_rect, pixmap, source_rect);
        if (mask_painter_ != nullptr) drawMaskForPixmap(target_rect, pixmap, source_rect);
      }
  }
  void drawPixmap(int x, int y, int w, int h, const QPixmap &pm, int sx, int sy, int sw, int sh)
  {
    partial_painters_.drawPixmap(x, y, w, h, pm, sx, sy, sw, sh);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(x, y, w, h, pm, sx, sy, sw, sh);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRect(x, y, w, h), pm, QRect(sx, sy, sw, sh));
      }
  }
  void drawPixmap(int x, int y, const QPixmap &pm, int sx, int sy, int sw, int sh)
  {
    partial_painters_.drawPixmap(x, y, pm, sx, sy, sw, sh);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(x, y, pm, sx, sy, sw, sh);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRect(QPoint(x, y), pm.size()), pm, QRect(sx, sy, sw, sh));
      }
  }
  void drawPixmap(const QPointF &point, const QPixmap &pm, const QRectF &sr)
  {
    partial_painters_.drawPixmap(point, pm, sr);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(point, pm, sr);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRectF(point, pm.size()), pm, sr);
      }
  }
  void drawPixmap(const QPoint &point, const QPixmap &pm, const QRect &sr)
  {
    partial_painters_.drawPixmap(point, pm, sr);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(point, pm, sr);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRect(point, pm.size()), pm, sr);
      }
  }
  void drawPixmap(const QPointF &point, const QPixmap &pm)
  {
    partial_painters_.drawPixmap(point, pm);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(point, pm);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRectF(point, pm.size()), pm);
      }
  }
  void drawPixmap(const QPoint &point, const QPixmap &pm)
  {
    partial_painters_.drawPixmap(point, pm);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(point, pm);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRect(point, pm.size()), pm);
      }
  }
  void drawPixmap(int x, int y, const QPixmap &pm)
  {
    partial_painters_.drawPixmap(x, y, pm);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(x, y, pm);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRect(QPoint(x, y), pm.size()), pm);
      }
  }
  void drawPixmap(const QRect &r, const QPixmap &pm)
  {
    partial_painters_.drawPixmap(r, pm);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(r, pm);
        if (mask_painter_ != nullptr) drawMaskForPixmap(r, pm);
      }
  }
  void drawPixmap(int x, int y, int w, int h, const QPixmap &pm)
  {
    partial_painters_.drawPixmap(x, y, w, h, pm);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmap(x, y, w, h, pm);
        if (mask_painter_ != nullptr) drawMaskForPixmap(QRect(x, y, w, h), pm);
      }
  }

  void drawPixmapFragments(const QPainter::PixmapFragment *fragments, int fragment_count, const QPixmap &pixmap,
                           QPainter::PixmapFragmentHints hints = QPainter::PixmapFragmentHints())
  {
    partial_painters_.drawPixmapFragments(fragments, fragment_count, pixmap, hints);
    if (!p->partial_draw)
      {
        painter_.get().drawPixmapFragments(fragments, fragment_count, pixmap, hints);
        // TODO: Use rectangles to represent fragments in the mask image
      }
  }

  void drawImage(const QRectF &target_rect, const QImage &image, const QRectF &source_rect,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    partial_painters_.drawImage(target_rect, image, source_rect, flags);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(target_rect, image, source_rect, flags);
        if (mask_painter_ != nullptr) drawMaskForImage(target_rect, image, source_rect);
      }
  }
  void drawImage(const QRect &target_rect, const QImage &image, const QRect &source_rect,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    partial_painters_.drawImage(target_rect, image, source_rect, flags);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(target_rect, image, source_rect, flags);
        if (mask_painter_ != nullptr) drawMaskForImage(target_rect, image, source_rect);
      }
  }
  void drawImage(const QPointF &point, const QImage &image, const QRectF &sr,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    partial_painters_.drawImage(point, image, sr, flags);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(point, image, sr, flags);
        if (mask_painter_ != nullptr) drawMaskForImage(QRectF(point, image.size()), image, sr);
      }
  }
  void drawImage(const QPoint &point, const QImage &image, const QRect &sr,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    partial_painters_.drawImage(point, image, sr, flags);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(point, image, sr, flags);
        if (mask_painter_ != nullptr) drawMaskForImage(QRectF(point, image.size()), image, sr);
      }
  }
  void drawImage(const QRectF &r, const QImage &image)
  {
    partial_painters_.drawImage(r, image);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(r, image);
        if (mask_painter_ != nullptr) drawMaskForImage(r, image);
      }
  }
  void drawImage(const QRect &r, const QImage &image)
  {
    partial_painters_.drawImage(r, image);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(r, image);
        if (mask_painter_ != nullptr) drawMaskForImage(r, image);
      }
  }
  void drawImage(const QPointF &point, const QImage &image)
  {
    partial_painters_.drawImage(point, image);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(point, image);
        if (mask_painter_ != nullptr) drawMaskForImage(QRectF(point, image.size()), image);
      }
  }
  void drawImage(const QPoint &point, const QImage &image)
  {
    partial_painters_.drawImage(point, image);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(point, image);
        if (mask_painter_ != nullptr) drawMaskForImage(QRectF(point, image.size()), image);
      }
  }
  void drawImage(int x, int y, const QImage &image, int sx = 0, int sy = 0, int sw = -1, int sh = -1,
                 Qt::ImageConversionFlags flags = Qt::AutoColor)
  {
    partial_painters_.drawImage(x, y, image, sx, sy, sw, sh, flags);
    if (!p->partial_draw)
      {
        painter_.get().drawImage(x, y, image, sx, sy, sw, sh, flags);
        if (mask_painter_ != nullptr) drawMaskForImage(QRect(QPoint(x, y), image.size()), image);
      }
  }

  void setLayoutDirection(Qt::LayoutDirection direction) { paint(setLayoutDirection, direction); }

  void drawGlyphRun(const QPointF &position, const QGlyphRun &glyph_run) { paint(drawGlyphRun, position, glyph_run); }

  void drawStaticText(const QPointF &top_left_position, const QStaticText &static_text)
  {
    paint(drawStaticText, top_left_position, static_text);
  }
  void drawStaticText(const QPoint &top_left_position, const QStaticText &static_text)
  {
    paint(drawStaticText, top_left_position, static_text);
  }
  void drawStaticText(int left, int top, const QStaticText &static_text)
  {
    paint(drawStaticText, left, top, static_text);
  }

  void drawText(const QPointF &point, const QString &s) { paint(drawText, point, s); }
  void drawText(const QPoint &point, const QString &s) { paint(drawText, point, s); }
  void drawText(int x, int y, const QString &s) { paint(drawText, x, y, s); }

  void drawText(const QPointF &point, const QString &str, int tf, int justification_padding)
  {
    paint(drawText, point, str, tf, justification_padding);
  }

  void drawText(const QRectF &r, int flags, const QString &text, QRectF *br = nullptr)
  {
    paint(drawText, r, flags, text, br);
  }
  void drawText(const QRect &r, int flags, const QString &text, QRect *br = nullptr)
  {
    paint(drawText, r, flags, text, br);
  }
  void drawText(int x, int y, int w, int h, int flags, const QString &text, QRect *br = nullptr)
  {
    paint(drawText, x, y, w, h, flags, text, br);
  }

  void drawText(const QRectF &r, const QString &text, const QTextOption &o = QTextOption())
  {
    paint(drawText, r, text, o);
  }

  QRectF boundingRect(const QRectF &rect, int flags, const QString &text)
  {
    paint_and_return(boundingRect, rect, flags, text);
  }
  QRect boundingRect(const QRect &rect, int flags, const QString &text)
  {
    paint_and_return(boundingRect, rect, flags, text);
  }
  QRect boundingRect(int x, int y, int w, int h, int flags, const QString &text)
  {
    paint_and_return(boundingRect, x, y, w, h, flags, text);
  }

  QRectF boundingRect(const QRectF &rect, const QString &text, const QTextOption &o = QTextOption())
  {
    paint_and_return(boundingRect, rect, text, o);
  }

  void drawTextItem(const QPointF &point, const QTextItem &ti) { paint(drawTextItem, point, ti); }
  void drawTextItem(int x, int y, const QTextItem &ti) { paint(drawTextItem, x, y, ti); }
  void drawTextItem(const QPoint &point, const QTextItem &ti) { paint(drawTextItem, point, ti); }

  void fillRect(const QRectF &rect, const QBrush &brush)
  {
    partial_painters_.fillRect(rect, brush);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(rect, brush);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(rect, createMaskBrush(brush));
      }
  }
  void fillRect(int x, int y, int w, int h, const QBrush &brush)
  {
    partial_painters_.fillRect(x, y, w, h, brush);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(x, y, w, h, brush);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(x, y, w, h, createMaskBrush(brush));
      }
  }
  void fillRect(const QRect &rect, const QBrush &brush)
  {
    partial_painters_.fillRect(rect, brush);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(rect, brush);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(rect, createMaskBrush(brush));
      }
  }

  void fillRect(const QRectF &rect, const QColor &color)
  {
    partial_painters_.fillRect(rect, color);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(rect, color);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(rect, mask_painter_->brush().color());
      }
  }
  void fillRect(int x, int y, int w, int h, const QColor &color)
  {
    partial_painters_.fillRect(x, y, w, h, color);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(x, y, w, h, color);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(x, y, w, h, mask_painter_->brush().color());
      }
  }
  void fillRect(const QRect &rect, const QColor &color)
  {
    partial_painters_.fillRect(rect, color);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(rect, color);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(rect, mask_painter_->brush().color());
      }
  }

  void fillRect(int x, int y, int w, int h, Qt::GlobalColor c)
  {
    partial_painters_.fillRect(x, y, w, h, c);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(x, y, w, h, c);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(x, y, w, h, mask_painter_->brush().color());
      }
  }
  void fillRect(const QRect &r, Qt::GlobalColor c)
  {
    partial_painters_.fillRect(r, c);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(r, c);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(r, mask_painter_->brush().color());
      }
  }
  void fillRect(const QRectF &r, Qt::GlobalColor c)
  {
    partial_painters_.fillRect(r, c);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(r, c);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(r, mask_painter_->brush().color());
      }
  }

  void fillRect(int x, int y, int w, int h, Qt::BrushStyle style) { paint(fillRect, x, y, w, h, style); }
  void fillRect(const QRect &r, Qt::BrushStyle style) { paint(fillRect, r, style); }
  void fillRect(const QRectF &r, Qt::BrushStyle style) { paint(fillRect, r, style); }

#if QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)
  void fillRect(int x, int y, int w, int h, QGradient::Preset preset)
  {
    partial_painters_.fillRect(x, y, w, h, preset);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(x, y, w, h, preset);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(x, y, w, h, mask_painter_->brush().color());
      }
  }
  void fillRect(const QRect &r, QGradient::Preset preset)
  {
    partial_painters_.fillRect(r, preset);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(r, preset);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(r, mask_painter_->brush().color());
      }
  }
  void fillRect(const QRectF &r, QGradient::Preset preset)
  {
    partial_painters_.fillRect(r, preset);
    if (!p->partial_draw)
      {
        painter_.get().fillRect(r, preset);
        if (mask_painter_ != nullptr) mask_painter_->fillRect(r, mask_painter_->brush().color());
      }
  }
#endif

  void eraseRect(const QRectF &rect) { paint(eraseRect, rect); }
  void eraseRect(int x, int y, int w, int h) { paint(eraseRect, x, y, w, h); }
  void eraseRect(const QRect &rect) { paint(eraseRect, rect); }

  void setRenderHint(QPainter::RenderHint hint, bool on = true)
  {
    partial_painters_.setRenderHint(hint, on);
    if (!p->partial_draw)
      {
        painter_.get().setRenderHint(hint, on);
      }
  }
  void setRenderHints(QPainter::RenderHints hints, bool on = true)
  {
    partial_painters_.setRenderHints(hints, on);
    if (!p->partial_draw)
      {
        painter_.get().setRenderHints(hints, on);
      }
  }
  bool testRenderHint(QPainter::RenderHint hint) const { paint_and_return(testRenderHint, hint); }

  void beginNativePainting() { paint(beginNativePainting, ); }
  void endNativePainting() { paint(endNativePainting, ); }

  Qt::LayoutDirection layoutDirection() const { return painter_.get().layoutDirection(); }
  QPainter::RenderHints renderHints() const { return painter_.get().renderHints(); }
  QPaintEngine *paintEngine() const { return painter_.get().paintEngine(); }

#undef paint
#undef paint_and_return

protected:
  QBrush createMaskBrush(const QBrush &brush)
  {
    assert(mask_painter_ != nullptr);
    auto brush_ = QBrush(brush);
    brush_.setColor(mask_painter_->brush().color());
    return brush_;
  }

  QBrush createMaskBrush() { return createMaskBrush(painter_.get().brush()); }

  void applyBrushToMask()
  {
    if (mask_painter_ != nullptr) mask_painter_->setBrush(createMaskBrush());
  }

  QPen createMaskPen(const QPen &pen)
  {
    assert(mask_painter_ != nullptr);
    auto pen_ = QPen(pen);
    pen_.setColor(mask_painter_->pen().color());
    if (pen_.widthF() < 1.0) pen_.setWidth(1);
    return pen_;
  }

  QPen createMaskPen() { return createMaskPen(painter_.get().pen()); }

  void applyPenToMask()
  {
    if (mask_painter_ != nullptr) mask_painter_->setPen(createMaskPen());
  }

  void drawMaskForImage(const QRectF &target_rect, const QImage &image)
  {
    if (mask_painter_ != nullptr) drawMaskForImage(target_rect, image, QRectF(0, 0, image.width(), image.height()));
  }

  void drawMaskForImage(const QRectF &target_rect, const QImage &image, const QRectF &source_rect)
  {
    assert(mask_painter_ != nullptr);
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
    double image_device_pixel_ratio = image.devicePixelRatioF();
#elif QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
    double image_device_pixel_ratio = image.devicePixelRatio();
#else
    double image_device_pixel_ratio = 1.0;
#endif
    QTransform transform;
    transform.translate(target_rect.x(), target_rect.y());
    transform.scale((double)target_rect.width() / image.width() / image_device_pixel_ratio,
                    (double)target_rect.height() / image.height() / image_device_pixel_ratio);
    transform.scale((double)image.width() / source_rect.width(), (double)image.height() / source_rect.height());
    transform.translate(-source_rect.x(), -source_rect.y());
    transform *= mask_painter_->transform();
    transform = transform.inverted();
    auto clip_bounding_rect = mask_painter_->clipBoundingRect();
    auto clipped_target_rect = target_rect;
    if (!clip_bounding_rect.isEmpty()) clipped_target_rect = clip_bounding_rect.intersected(target_rect);
    auto transformed_target_rect = mask_painter_->transform().mapRect(clipped_target_rect);
    auto &group_image = group_mask_->image();
    QPainterPath transformed_clip_path;
    if (gkss->clip_region == GKS_K_REGION_ELLIPSE)
      {
        transformed_clip_path = mask_painter_->transform().map(mask_painter_->clipPath());
      }
    for (int j = transformed_target_rect.top(); j <= transformed_target_rect.bottom(); ++j)
      {
        if (j < 0 || j >= group_image.height()) continue;
        for (int i = transformed_target_rect.left(); i <= transformed_target_rect.right(); ++i)
          {
            if (i < 0 || i >= group_image.width()) continue;
            if (!transformed_clip_path.isEmpty() && !transformed_clip_path.contains(QPointF(i, j))) continue;
            qreal x, y;
            transform.map(i, j, &x, &y);
            if (x < source_rect.left() || x >= source_rect.right() || y < source_rect.top() ||
                y >= source_rect.bottom())
              continue;
            if ((image.pixel((int)x, (int)y) & 0xFF000000) == 0) continue;
            group_image.setPixel(i, j, mask_painter_->brush().color().rgb());
          }
      }
  }

  void drawMaskForPixmap(const QRectF &target_rect, const QPixmap &pixmap)
  {
    drawMaskForPixmap(target_rect, pixmap, QRectF(0, 0, pixmap.width(), pixmap.height()));
  }

  void drawMaskForPixmap(const QRectF &target_rect, const QPixmap &pixmap, const QRectF &source_rect)
  {
    drawMaskForImage(target_rect, pixmap.toImage(), source_rect);
  }

private:
  std::unique_ptr<QPainter> owned_painter_;
  std::reference_wrapper<QPainter> painter_;
  std::unique_ptr<GroupMask> group_mask_;
  QPainter *mask_painter_ = nullptr;
  std::stack<unsigned int> group_stack_;
  PartialPainters partial_painters_;
  std::unique_ptr<QPixmap> background_;
};

static void set_norm_xform(int tnr, double *wn, double *vp)
{
  double xp1, yp1, xp2, yp2;

  a[tnr] = (vp[1] - vp[0]) / (wn[1] - wn[0]);
  b[tnr] = vp[0] - wn[0] * a[tnr];
  c[tnr] = (vp[3] - vp[2]) / (wn[3] - wn[2]);
  d[tnr] = vp[2] - wn[2] * c[tnr];

  NDC_to_DC(vp[0], vp[3], xp1, yp1);
  NDC_to_DC(vp[1], vp[2], xp2, yp2);

  p->rect[tnr].setCoords(xp1, yp1, xp2, yp2);
}

static void init_norm_xform(void)
{
  int tnr;

  for (tnr = 0; tnr < MAX_TNR; tnr++) set_norm_xform(tnr, gkss->window[tnr], gkss->viewport[tnr]);
}

static void resize_window(void)
{
  p->mwidth = p->viewport[1] - p->viewport[0];
  p->width = nint(p->device_dpi_x * p->mwidth / 0.0254);
  if (p->width < 2)
    {
      p->width = 2;
      p->mwidth = (double)p->width / p->device_dpi_x * 0.0254;
    }

  p->mheight = p->viewport[3] - p->viewport[2];
  p->height = nint(p->device_dpi_y * p->mheight / 0.0254);
  if (p->height < 2)
    {
      p->height = 2;
      p->mheight = (double)p->height / p->device_dpi_y * 0.0254;
    }
  p->nominal_size = min(p->width, p->height) / 500.0;
  if (gkss->nominal_size > 0) p->nominal_size *= gkss->nominal_size;

#ifndef QT_PLUGIN_USED_AS_PLUGIN_CODE
  if (p->pixmap)
    {
      if (fabs(p->width * p->device_pixel_ratio - p->pixmap->size().width()) > FEPS ||
          fabs(p->height * p->device_pixel_ratio - p->pixmap->size().height()) > FEPS)
        {
          QPixmap *pixmap = new QPixmap(p->width * p->device_pixel_ratio, p->height * p->device_pixel_ratio);
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
          pixmap->setDevicePixelRatio(p->device_pixel_ratio);
#endif
          pixmap->fill(Qt::white);
          p->painter->assign(*pixmap, *p->paint_device);
          delete p->pixmap;
          p->pixmap = pixmap;

          p->painter->clearBackground();
          p->painter->setClipRect(0, 0, p->width, p->height);
        }
    }
#endif
}

static void set_xform(void)
{
  double ratio, w, h, x, y;

  ratio = (p->window[1] - p->window[0]) / (p->window[3] - p->window[2]) * (1.0 * p->device_dpi_x / p->device_dpi_y);

  if (p->width > p->height * ratio)
    {
      w = p->height * ratio;
      h = p->height;
      x = 0.5 * (p->width - w);
      y = h;
    }
  else
    {
      w = p->width;
      h = p->width / ratio;
      x = 0;
      y = h + 0.5 * (p->height - h);
    }

  p->a = w / (p->window[1] - p->window[0]);
  p->b = x - p->window[0] * p->a;
  p->c = h / (p->window[2] - p->window[3]);
  p->d = y + p->window[2] * p->c;

  p->nominal_size = min(p->width, p->height) / 500.0;
  if (gkss->nominal_size > 0) p->nominal_size *= gkss->nominal_size;
}

static void seg_xform(double *x, double *y)
{
  double xx;

  xx = *x * gkss->mat[0][0] + *y * gkss->mat[0][1] + gkss->mat[2][0];
  *y = *x * gkss->mat[1][0] + *y * gkss->mat[1][1] + gkss->mat[2][1];
  *x = xx;
}

static void seg_xform_rel(double *x, double *y)
{
  double xx;

  xx = *x * gkss->mat[0][0] + *y * gkss->mat[0][1];
  *y = *x * gkss->mat[1][0] + *y * gkss->mat[1][1];
  *x = xx;
}

static void set_clip_rect(int tnr)
{
  if (gkss->clip_tnr != 0)
    tnr = gkss->clip_tnr;
  else if (gkss->clip == GKS_K_NOCLIP)
    tnr = 0;

  if (gkss->clip_region == GKS_K_REGION_ELLIPSE && tnr != 0)
    {
      if (gkss->clip_start_angle > 0 || gkss->clip_end_angle < 360)
        {
          QPainterPath path;
          path.moveTo(p->rect[tnr].center());
          path.arcTo(p->rect[tnr].toRect(), gkss->clip_start_angle, gkss->clip_end_angle - gkss->clip_start_angle);
          p->painter->setClipPath(path);
        }
      else
        p->painter->setClipRegion(QRegion(p->rect[tnr].toRect(), QRegion::Ellipse));
    }
  else
    {
      p->painter->setClipRect(p->rect[tnr]);
    }
}

static void set_color_rep(int color, double red, double green, double blue)
{
  int i;

  if (color >= 0 && color < MAX_COLOR)
    {
      p->rgb[color].setRgb(nint(red * 255), nint(green * 255), nint(blue * 255));
      for (i = 0; i < PATTERNS; i++)
        {
          if (p->pcolor[i] == color) p->pcolor[i] = -1;
        }
    }
}

static void init_colors(void)
{
  int color;
  double red, green, blue;

  for (color = 0; color < MAX_COLOR; color++)
    {
      gks_inq_rgb(color, &red, &green, &blue);
      set_color_rep(color, red, green, blue);
    }
}

static void set_color(int color)
{
  QColor transparent_color(p->rgb[color]);
  transparent_color.setAlpha(p->transparency);
  p->painter->setPen(transparent_color);
  p->painter->setBrush(transparent_color);
}

static QPixmap *create_pattern(int pattern, int color)
{
  int parray[33];
  int i, j;
  QPixmap *pm;

  gks_inq_pattern_array(pattern, parray);

  QImage img(8, 8, QImage::Format_Mono);
  img.setColor(0, qRgb(255, 255, 255));
  img.setColor(1, p->rgb[color].rgb());
  for (i = 0; i < 8; i++)
    for (j = 0; j < 8; j++) img.setPixel(i, j, (parray[(j % parray[0]) + 1] >> i) & 0x01 ? 0 : 1);

  pm = new QPixmap(8, 8);
  QPixmap tmp = QPixmap::fromImage(img);
  *pm = tmp;

  return pm;
}

static void line_routine(int n, double *px, double *py, int linetype, int tnr)
{
  double x, y, x0, y0, xi, yi, xim1, yim1;
  int i;

  WC_to_NDC(px[0], py[0], tnr, x, y);
  seg_xform(&x, &y);
  NDC_to_DC(x, y, x0, y0);

  p->npoints = 0;
  (*p->points)[p->npoints++] = QPointF(x0, y0);

  xim1 = x0;
  yim1 = y0;
  for (i = 1; i < n; i++)
    {
      WC_to_NDC(px[i], py[i], tnr, x, y);
      seg_xform(&x, &y);
      NDC_to_DC(x, y, xi, yi);

      if (i == 1 || xi != xim1 || yi != yim1)
        {
          (*p->points)[p->npoints++] = QPointF(xi, yi);
          xim1 = xi;
          yim1 = yi;
        }
    }
  if (linetype == 0) (*p->points)[p->npoints++] = QPointF(x0, y0);

  auto ln_type = gkss->asf[0] ? gkss->ltype : gkss->lindex;
  if (p->npoints > MAX_POINTS_PERFORMANCE_THRESHOLD && ln_type == GKS_K_LINETYPE_SOLID)
    {
      /*
       * Qt drawPolyline() is slow on calculating line joins for a large list of points.
       * Drawing each of the line segments using drawLine() does remove the problem.
       */
      for (i = 1; i < p->npoints; i++) p->painter->drawLine((*p->points)[i - 1], (*p->points)[i]);
    }
  else
    {
      p->painter->drawPolyline(p->points->constData(), p->npoints);
    }
#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
  if (!p->bounding_stack.empty())
    {
      double point_x, point_y;
      for (i = 0; i < p->npoints; i++)
        {
          point_x = p->points->constData()[i].x();
          point_y = p->points->constData()[i].y();
          if (p->bounding_stack.top().x_max < point_x) p->bounding_stack.top().x_max = point_x;
          if (p->bounding_stack.top().x_min > point_x) p->bounding_stack.top().x_min = point_x;
          if (p->bounding_stack.top().y_max < point_y) p->bounding_stack.top().y_max = point_y;
          if (p->bounding_stack.top().y_min > point_y) p->bounding_stack.top().y_min = point_y;
        }

      /* A vertical or horizontal polyline needs a bigger bounding box inside gredit so that the user can click on it */
      double min_bbox_size = 8;
      if (p->bounding_stack.top().x_max - p->bounding_stack.top().x_min < min_bbox_size)
        {
          p->bounding_stack.top().x_min -= min_bbox_size / 2;
          p->bounding_stack.top().x_max += min_bbox_size / 2;
        }
      if (p->bounding_stack.top().y_max - p->bounding_stack.top().y_min < min_bbox_size)
        {
          p->bounding_stack.top().y_min -= min_bbox_size / 2;
          p->bounding_stack.top().y_max += min_bbox_size / 2;
        }
    }
#endif
}

static void polyline(int n, double *px, double *py)
{
  int ln_type, ln_color;
  double ln_width;
  int i, list[10];

  if (n > p->max_points)
    {
      p->points->resize(n);
      p->max_points = n;
    }
  ln_type = gkss->asf[0] ? gkss->ltype : gkss->lindex;
  ln_color = gkss->asf[2] ? gkss->plcoli : 1;
  if (ln_color < 0 || ln_color >= MAX_COLOR) ln_color = 1;
  ln_width = gkss->asf[1] ? gkss->lwidth : 1;

  ln_width *= p->nominal_size;
  /* line widths < 0.1 no longer provide meaningful results */
  if (ln_width < 0.1) ln_width = 0.1;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);
  QColor transparent_color(p->rgb[ln_color]);
  transparent_color.setAlpha(p->transparency);

  if (ln_type != GKS_K_LINETYPE_SOLID)
    {
      gks_get_dash_list(ln_type, 1.0, list);
      QVector<qreal> dashPattern(list[0]);
      for (i = 0; i < list[0]; i++) dashPattern[i] = (double)list[i + 1];

      QPen pen(QPen(transparent_color, ln_width, Qt::CustomDashLine, Qt::FlatCap, Qt::RoundJoin));
      pen.setDashPattern(dashPattern);
      p->painter->setPen(pen);
    }
  else
    p->painter->setPen(QPen(transparent_color, ln_width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));

  line_routine(n, px, py, ln_type, gkss->cntnr);

  p->painter->restore();
}

static void draw_marker(double xn, double yn, int mtype, double mscale, int mcolor)
{
  double x, y;
  int i;
  int pc, op;
  double r, d, scale, xr, yr;
  QPolygonF *points;

#include "marker.h"

  QColor marker_color(p->rgb[mcolor]);
  marker_color.setAlpha(p->transparency);
  QColor border_color(p->rgb[gkss->bcoli]);
  border_color.setAlpha(p->transparency);

  mscale *= p->nominal_size;
  r = 3 * mscale;
  d = 2 * r;
  scale = 0.01 * mscale / 3.0;

  xr = r;
  yr = 0;
  seg_xform_rel(&xr, &yr);
  r = sqrt(xr * xr + yr * yr);

  NDC_to_DC(xn, yn, x, y);

  pc = 0;
  mtype = (d > 0) ? mtype + marker_off : marker_off + 1;

  do
    {
      op = marker[mtype][pc];
      switch (op)
        {

        case 1: /* point */
          p->painter->setPen(QPen(marker_color, p->nominal_size, Qt::SolidLine, Qt::FlatCap));
          p->painter->drawPoint(QPointF(x, y));
          break;

        case 2: /* line */
          for (i = 0; i < 2; i++)
            {
              xr = scale * marker[mtype][pc + 2 * i + 1];
              yr = -scale * marker[mtype][pc + 2 * i + 2];
              seg_xform_rel(&xr, &yr);
              (*p->points)[i] = QPointF(x - xr, y + yr);
            }
          p->painter->setPen(
              QPen(marker_color, max(gkss->bwidth, gkss->lwidth) * p->nominal_size, Qt::SolidLine, Qt::FlatCap));
          p->painter->drawPolyline(p->points->constData(), 2);
          pc += 4;
          break;

        case 3: /* polygon */
        case 9: /* border polygon */
          if (op == 3 || gkss->bwidth > 0)
            {
              points = new QPolygonF(marker[mtype][pc + 1]);
              for (i = 0; i < marker[mtype][pc + 1]; i++)
                {
                  xr = scale * marker[mtype][pc + 2 + 2 * i];
                  yr = -scale * marker[mtype][pc + 3 + 2 * i];
                  seg_xform_rel(&xr, &yr);
                  (*points)[i] = QPointF(x - xr, y + yr);
                }
              p->painter->setPen(QPen(op == 3 ? marker_color : border_color, gkss->bwidth * p->nominal_size,
                                      Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
              p->painter->drawPolyline(points->constData(), marker[mtype][pc + 1]);
              delete points;
            }
          pc += 1 + 2 * marker[mtype][pc + 1];
          break;

        case 4: /* filled polygon */
        case 5: /* hollow polygon */
          points = new QPolygonF(marker[mtype][pc + 1]);
          if (op == 4)
            {
              p->painter->setBrush(QBrush(marker_color, Qt::SolidPattern));
              if (gkss->bcoli != gkss->pmcoli && gkss->bwidth > 0)
                p->painter->setPen(
                    QPen(border_color, gkss->bwidth * p->nominal_size, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
              else
                p->painter->setPen(Qt::NoPen);
            }
          else
            set_color(0);
          for (i = 0; i < marker[mtype][pc + 1]; i++)
            {
              xr = scale * marker[mtype][pc + 2 + 2 * i];
              yr = -scale * marker[mtype][pc + 3 + 2 * i];
              seg_xform_rel(&xr, &yr);
              (*points)[i] = QPointF(x - xr, y + yr);
            }
          p->painter->drawPolygon(points->constData(), marker[mtype][pc + 1]);
          pc += 1 + 2 * marker[mtype][pc + 1];
          delete points;
          break;

        case 6: /* arc */
          p->painter->setPen(
              QPen(marker_color, max(gkss->bwidth, gkss->lwidth) * p->nominal_size, Qt::SolidLine, Qt::FlatCap));
          p->painter->drawArc(QRectF(x - r, y - r, d, d), 0, 360 * 16);
          break;

        case 7: /* filled arc */
        case 8: /* hollow arc */
          if (op == 7)
            {
              p->painter->setBrush(QBrush(marker_color, Qt::SolidPattern));
              if (gkss->bcoli != gkss->pmcoli && gkss->bwidth > 0)
                p->painter->setPen(QPen(border_color, gkss->bwidth * p->nominal_size, Qt::SolidLine, Qt::FlatCap));
              else
                p->painter->setPen(Qt::NoPen);
            }
          else
            set_color(0);
          p->painter->drawChord(QRectF(x - r, y - r, d, d), 0, 360 * 16);
          break;
        }
#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
      if (!p->bounding_stack.empty())
        {
          double point_x, point_y;
          point_x = x;
          point_y = y;
          if (p->bounding_stack.top().x_max <= point_x) p->bounding_stack.top().x_max = point_x;
          if (p->bounding_stack.top().x_min >= point_x) p->bounding_stack.top().x_min = point_x;
          if (p->bounding_stack.top().y_max <= point_y) p->bounding_stack.top().y_max = point_y;
          if (p->bounding_stack.top().y_min >= point_y) p->bounding_stack.top().y_min = point_y;
        }
#endif
      pc++;
    }
  while (op != 0);
}

static void marker_routine(int n, double *px, double *py, int mtype, double mscale, int mcolor)
{
  double x, y;
  double *clrt = gkss->viewport[gkss->cntnr];
  int i, draw;

  for (i = 0; i < n; i++)
    {
      WC_to_NDC(px[i], py[i], gkss->cntnr, x, y);
      seg_xform(&x, &y);

      if (gkss->clip == GKS_K_CLIP)
        draw = (x >= clrt[0] && x <= clrt[1] && y >= clrt[2] && y <= clrt[3]);
      else
        draw = 1;

      if (draw) draw_marker(x, y, mtype, mscale, mcolor);
    }
}

static void polymarker(int n, double *px, double *py)
{
  int mk_type, mk_color;
  double mk_size;

  mk_type = gkss->asf[3] ? gkss->mtype : gkss->mindex;
  mk_size = gkss->asf[4] ? gkss->mszsc : 1;
  mk_color = gkss->asf[5] ? gkss->pmcoli : 1;

  if (mk_color < 0 || mk_color >= MAX_COLOR) mk_color = 1;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);

  marker_routine(n, px, py, mk_type, mk_size, mk_color);

  p->painter->restore();
}

static void text_routine(double x, double y, int nchars, char *chars)
{
  int i, ch, width;
  double xrel, yrel, xstart, ystart, ax, ay;
  QFontMetrics fm = QFontMetrics(*p->font);
  QString s = QString("");
  if (p->family == 3)
    {
      /* Open Symbol maps codepoints to glyphs correctly, but GKS expects mappings like a to alpha, which we need to
       * revert */
      for (i = 0; i < nchars; i++)
        {
          ch = chars[i];
          if (ch < 0) ch += 256;
          ch = symbol2utf[ch];
          s.append(QChar(ch));
        }
    }
  else
    {
      s = QString::fromUtf8(chars);
    }

  (void)nchars;

  NDC_to_DC(x, y, xstart, ystart);

#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
  width = fm.horizontalAdvance(s);
#else
  width = fm.width(s);
#endif
  xrel = width * xfac[gkss->txal[0]];
  yrel = p->capheight * yfac[gkss->txal[1]];
  CharXform(xrel, yrel, ax, ay);
  xstart += ax;
  ystart -= ay;

  if (fabs(p->angle) > FEPS)
    {
      p->painter->save();
      p->painter->translate(xstart, ystart);
      p->painter->rotate(-p->angle);
      p->painter->drawText(0, 0, s);
      p->painter->restore();
    }
  else
    p->painter->drawText(xstart, ystart, s);

#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
  if (!p->bounding_stack.empty())
    {
      p->bounding_stack.top().x_max = xstart + xrel;
      p->bounding_stack.top().x_min = xstart;
      p->bounding_stack.top().y_max = ystart + yrel;
      p->bounding_stack.top().y_min = ystart;
    }
#endif
}

static void set_font(int font)
{
  double scale, ux, uy;
  int fontNum, size, bold, italic;
  double width, height, capheight;

  font = abs(font);
  if (font >= 101 && font <= 129)
    font -= 100;
  else if (font >= 1 && font <= 32)
    font = map[font - 1];
  else
    font = 9;

  WC_to_NDC_rel(gkss->chup[0], gkss->chup[1], gkss->cntnr, ux, uy);
  seg_xform_rel(&ux, &uy);

  p->alpha = -atan2(ux, uy);
  p->angle = p->alpha * 180 / M_PI;
  if (p->angle < 0) p->angle += 360;

  scale = sqrt(gkss->chup[0] * gkss->chup[0] + gkss->chup[1] * gkss->chup[1]);
  ux = gkss->chup[0] / scale * gkss->chh;
  uy = gkss->chup[1] / scale * gkss->chh;
  WC_to_NDC_rel(ux, uy, gkss->cntnr, ux, uy);

  width = 0;
  height = sqrt(ux * ux + uy * uy);
  seg_xform_rel(&width, &height);

  height = sqrt(width * width + height * height);
  capheight = height * (fabs(p->c) + 1);
  p->capheight = nint(capheight);

  fontNum = font - 1;
  size = nint(p->capheight / capheights[fontNum]);
  if (size < 1) size = 1;
  if (font > 13) font += 3;
  p->family = (font - 1) / 4;
  bold = (font % 4 == 1 || font % 4 == 2) ? 0 : 1;
  italic = (font % 4 == 2 || font % 4 == 0);

  p->font->setFamily(fonts[p->family]);
  p->font->setBold(bold);
  p->font->setItalic(italic);
  p->font->setPixelSize(size);

  p->painter->setFont(*p->font);
}

static void fill_routine(int n, double *px, double *py, int tnr);

static void text(double px, double py, int nchars, char *chars)
{
  int tx_font, tx_prec, tx_color;
  double x, y;

  tx_font = gkss->asf[6] ? gkss->txfont : predef_font[gkss->tindex - 1];
  tx_prec = gkss->asf[6] ? gkss->txprec : predef_prec[gkss->tindex - 1];
  tx_color = gkss->asf[9] ? gkss->txcoli : 1;

  if (tx_color < 0 || tx_color >= MAX_COLOR) tx_color = 1;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);
  QColor transparent_color(p->rgb[tx_color]);
  transparent_color.setAlpha(p->transparency);
  p->painter->setPen(QPen(transparent_color, p->nominal_size, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));

  if (tx_prec == GKS_K_TEXT_PRECISION_STRING)
    {
      set_font(tx_font);

      WC_to_NDC(px, py, gkss->cntnr, x, y);
      seg_xform(&x, &y);

      text_routine(x, y, nchars, chars);
    }
  else
    {
      if ((tx_prec == GKS_K_TEXT_PRECISION_STROKE || tx_prec == GKS_K_TEXT_PRECISION_CHAR) && fontfile == 0)
        {
          fontfile = gks_open_font();
          gkss->fontfile = fontfile;
        }
      gks_emul_text(px, py, nchars, chars, line_routine, fill_routine);
    }

  p->painter->restore();
}

static void fill_routine(int n, double *px, double *py, int tnr)
{
  int i;
  double x, y, xi, yi;
  QPolygonF *points;

  points = new QPolygonF();
  for (i = 0; i < n; i++)
    {
      if (isnan(px[i]) || isnan(py[i]))
        {
          NDC_to_DC(0, 0, xi, yi);
          points->append(QPointF(xi, yi));
          continue;
        }
      WC_to_NDC(px[i], py[i], tnr, x, y);
      seg_xform(&x, &y);
      NDC_to_DC(x, y, xi, yi);
      points->append(QPointF(xi, yi));
    }

  if (points->size() >= 2)
    {
      p->painter->drawPolygon(points->constData(), points->size());
    }

#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
  if (!p->bounding_stack.empty())
    {
      double point_x, point_y;
      for (i = 0; i < points->size() - 1; i++)
        {
          point_x = points->constData()[i].x();
          point_y = points->constData()[i].y();
          if (p->bounding_stack.top().x_max < point_x) p->bounding_stack.top().x_max = point_x;
          if (p->bounding_stack.top().x_min > point_x) p->bounding_stack.top().x_min = point_x;
          if (p->bounding_stack.top().y_max < point_y) p->bounding_stack.top().y_max = point_y;
          if (p->bounding_stack.top().y_min > point_y) p->bounding_stack.top().y_min = point_y;
        }
    }
#endif
  delete points;
}

static void fillarea(int n, double *px, double *py)
{
  int fl_inter, fl_style, fl_color;

  fl_inter = gkss->asf[10] ? gkss->ints : predef_ints[gkss->findex - 1];
  fl_style = gkss->asf[11] ? gkss->styli : predef_styli[gkss->findex - 1];
  fl_color = gkss->asf[12] ? gkss->facoli : 1;

  if (fl_color < 0 || fl_color >= MAX_COLOR) fl_color = 1;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);
  QColor transparent_color(p->rgb[fl_color]);
  transparent_color.setAlpha(p->transparency);

  if (fl_inter == GKS_K_INTSTYLE_HOLLOW)
    {
      p->painter->setPen(
          QPen(transparent_color, gkss->bwidth * p->nominal_size, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
      line_routine(n, px, py, DrawBorder, gkss->cntnr);
    }
  else if (fl_inter == GKS_K_INTSTYLE_SOLID)
    {
      p->painter->setPen(Qt::NoPen);
      p->painter->setBrush(QBrush(transparent_color, Qt::SolidPattern));
      fill_routine(n, px, py, gkss->cntnr);
    }
  else if (fl_inter == GKS_K_INTSTYLE_PATTERN || fl_inter == GKS_K_INTSTYLE_HATCH)
    {
      if (fl_inter == GKS_K_INTSTYLE_HATCH) fl_style += HATCH_STYLE;
      if (fl_style >= PATTERNS) fl_style = 1;
      if (p->pattern[fl_style] == NULL || p->pcolor[fl_style] != fl_color)
        {
          if (p->pattern[fl_style] != NULL) free(p->pattern[fl_style]);
          p->pattern[fl_style] = create_pattern(fl_style, fl_color);
          p->pcolor[fl_style] = fl_color;
        }
      p->painter->setPen(Qt::NoPen);
      p->painter->setBrush(QBrush(transparent_color, *p->pattern[fl_style]));
      fill_routine(n, px, py, gkss->cntnr);
    }

  p->painter->restore();
}

static void cellarray(double xmin, double xmax, double ymin, double ymax, int dx, int dy, int dimx, int *colia,
                      int true_color)
{
  double x1, y1, x2, y2;
  double xi1, xi2, yi1, yi2;
  double x, y;
  int width, height;
  int i, j, ix, iy, ind;
  int swapx, swapy;

  WC_to_NDC(xmin, ymax, gkss->cntnr, x1, y1);
  seg_xform(&x1, &y1);
  NDC_to_DC(x1, y1, xi1, yi1);

  WC_to_NDC(xmax, ymin, gkss->cntnr, x2, y2);
  seg_xform(&x2, &y2);
  NDC_to_DC(x2, y2, xi2, yi2);

  width = nint(abs(xi2 - xi1) * p->device_pixel_ratio);
  height = nint(abs(yi2 - yi1) * p->device_pixel_ratio);
  if (width == 0 || height == 0) return;
  x = min(xi1, xi2);
  y = min(yi1, yi2);

  swapx = xi1 > xi2;
  swapy = yi1 < yi2;

#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
  if (!p->bounding_stack.empty())
    {
      p->bounding_stack.top().x_max = xi2;
      p->bounding_stack.top().x_min = xi1;
      if (swapy)
        {
          p->bounding_stack.top().y_max = yi2;
          p->bounding_stack.top().y_min = yi1;
        }
      else
        {
          p->bounding_stack.top().y_max = yi1;
          p->bounding_stack.top().y_min = yi2;
        }
    }
#endif

  if (!true_color)
    {
      QImage img = QImage(width, height, QImage::Format_ARGB32);
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
      img.setDevicePixelRatio(p->device_pixel_ratio);
#endif
      for (j = 0; j < height; j++)
        {
          iy = dy * j / height;
          if (swapy) iy = dy - 1 - iy;
          for (i = 0; i < width; i++)
            {
              ix = dx * i / width;
              if (swapx) ix = dx - 1 - ix;
              ind = colia[iy * dimx + ix];
              ind = FIX_COLORIND(ind);
              QColor transparent_color(p->rgb[ind]);
              transparent_color.setAlpha(p->transparency);
              img.setPixel(i, j, transparent_color.rgba());
            }
        }
      p->painter->drawPixmap(QPointF(x, y), QPixmap::fromImage(img));
    }
  else
    {
      unsigned char *pixels = (unsigned char *)gks_malloc(width * height * 4);
      gks_resample((const unsigned char *)colia, pixels, dx, dy, width, height, dimx, swapx, swapy,
                   gkss->resample_method);
      /* TODO: Use QImage::Format_RGBA8888 once GR stops supporting Qt4? */
      for (j = 0; j < height; j++)
        {
          for (i = 0; i < width; i++)
            {
              unsigned char red = pixels[(j * width + i) * 4 + 0];
              unsigned char green = pixels[(j * width + i) * 4 + 1];
              unsigned char blue = pixels[(j * width + i) * 4 + 2];
              unsigned char alpha = (unsigned char)(pixels[(j * width + i) * 4 + 3] * gkss->alpha);

              ((unsigned int *)pixels)[j * width + i] = (alpha << 24u) + (red << 16u) + (green << 8u) + (blue << 0u);
            }
        }
      QImage img = QImage(pixels, width, height, QImage::Format_ARGB32);
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
      img.setDevicePixelRatio(p->device_pixel_ratio);
#endif
      p->painter->drawPixmap(QPointF(x, y), QPixmap::fromImage(img));
      gks_free(pixels);
    }
}

static void to_DC(int n, double *x, double *y)
{
  int i;
  double xn, yn;

  for (i = 0; i < n; i++)
    {
      WC_to_NDC(x[i], y[i], gkss->cntnr, xn, yn);
      seg_xform(&xn, &yn);
      x[i] = p->a * xn + p->b;
      y[i] = p->c * yn + p->d;
    }
}

static void draw_path(int n, double *px, double *py, int nc, int *codes)
{
  int i, j;
  double x[3], y[3], w, h, a1, a2;
  double cur_x = 0, cur_y = 0;
  double start_x = 0, start_y = 0;
  QPainterPath path;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);

  QColor stroke_color(p->rgb[gkss->bcoli]);
  stroke_color.setAlpha(p->transparency);
  QColor fill_color(p->rgb[gkss->facoli]);
  fill_color.setAlpha(p->transparency);

  j = 0;
  for (i = 0; i < nc; ++i)
    {
      assert(j <= n);
      switch (codes[i])
        {
        case 'M':
        case 'm':
          x[0] = px[j];
          y[0] = py[j];
          if (codes[i] == 'm')
            {
              x[0] += cur_x;
              y[0] += cur_y;
            }
          cur_x = start_x = x[0];
          cur_y = start_y = y[0];
          to_DC(1, x, y);
          path.moveTo(x[0], y[0]);
          j += 1;
          break;
        case 'L':
        case 'l':
          x[0] = px[j];
          y[0] = py[j];
          if (codes[i] == 'l')
            {
              x[0] += cur_x;
              y[0] += cur_y;
            }
          cur_x = x[0];
          cur_y = y[0];
          to_DC(1, x, y);
          path.lineTo(x[0], y[0]);
          j += 1;
          break;
        case 'Q':
        case 'q':
          x[0] = px[j];
          y[0] = py[j];
          if (codes[i] == 'q')
            {
              x[0] += cur_x;
              y[0] += cur_y;
            }
          x[1] = px[j + 1];
          y[1] = py[j + 1];
          if (codes[i] == 'q')
            {
              x[1] += cur_x;
              y[1] += cur_y;
            }
          cur_x = x[1];
          cur_y = y[1];
          to_DC(2, x, y);
          path.quadTo(x[0], y[0], x[1], y[1]);
          j += 2;
          break;
        case 'C':
        case 'c':
          x[0] = px[j];
          y[0] = py[j];
          if (codes[i] == 'c')
            {
              x[0] += cur_x;
              y[0] += cur_y;
            }
          x[1] = px[j + 1];
          y[1] = py[j + 1];
          if (codes[i] == 'c')
            {
              x[1] += cur_x;
              y[1] += cur_y;
            }
          x[2] = px[j + 2];
          y[2] = py[j + 2];
          if (codes[i] == 'c')
            {
              x[2] += cur_x;
              y[2] += cur_y;
            }
          cur_x = x[2];
          cur_y = y[2];
          to_DC(3, x, y);
          path.cubicTo(x[0], y[0], x[1], y[1], x[2], y[2]);
          j += 3;
          break;
        case 'A':
        case 'a':
          {
            double rx, ry, cx, cy;
            rx = fabs(px[j]);
            ry = fabs(py[j]);
            a1 = px[j + 1];
            a2 = py[j + 1];
            cx = cur_x - rx * cos(a1);
            cy = cur_y - ry * sin(a1);
            x[0] = cx - rx;
            y[0] = cy - ry;
            x[1] = cx + rx;
            y[1] = cy + ry;
            cur_x = cx + rx * cos(a2);
            cur_y = cy + ry * sin(a2);
          }
          to_DC(2, x, y);
          w = x[1] - x[0];
          h = y[1] - y[0];
          a1 *= -180 / M_PI;
          a2 *= -180 / M_PI;
          while (fabs(a2 - a1) > 360)
            {
              if (a1 > a2)
                {
                  path.arcTo(x[0], y[0], w, h, a1, -180);
                  a1 -= 180;
                }
              else
                {
                  path.arcTo(x[0], y[0], w, h, a1, 180);
                  a1 += 180;
                }
            }
          path.arcTo(x[0], y[0], w, h, a1, (a2 - a1));
          j += 3;
          break;
        case 's': /* close and stroke */
          path.closeSubpath();
          cur_x = start_x;
          cur_y = start_y;
          p->painter->strokePath(
              path, QPen(stroke_color, gkss->bwidth * p->nominal_size, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
          break;
        case 'S': /* stroke */
          p->painter->strokePath(
              path, QPen(stroke_color, gkss->bwidth * p->nominal_size, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
          break;
        case 'F': /* fill (even-odd) and stroke */
        case 'G': /* fill (winding) and stroke */
          path.closeSubpath();
          cur_x = start_x;
          cur_y = start_y;
          path.setFillRule(codes[i] == 'F' ? Qt::OddEvenFill : Qt::WindingFill);
          p->painter->fillPath(path, fill_color);
          p->painter->strokePath(
              path, QPen(stroke_color, gkss->bwidth * p->nominal_size, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
          break;
        case 'f': /* fill (even-odd) */
        case 'g': /* fill (winding) */
          path.closeSubpath();
          cur_x = start_x;
          cur_y = start_y;
          path.setFillRule(codes[i] == 'f' ? Qt::OddEvenFill : Qt::WindingFill);
          p->painter->fillPath(path, fill_color);
          break;
        case 'Z': /* closepath */
          path.closeSubpath();
          cur_x = start_x;
          cur_y = start_y;
          break;
        case '\0':
          break;
        default:
          gks_perror("invalid path code ('%c')", codes[i]);
          exit(1);
        }
    }

#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
  if (!p->bounding_stack.empty())
    {
      if (p->bounding_stack.top().x_max < path.boundingRect().x() + path.boundingRect().width())
        p->bounding_stack.top().x_max = path.boundingRect().x() + path.boundingRect().width();
      if (p->bounding_stack.top().x_min > path.boundingRect().x())
        p->bounding_stack.top().x_min = path.boundingRect().x();
      if (p->bounding_stack.top().y_max < path.boundingRect().y() + path.boundingRect().height())
        p->bounding_stack.top().y_max = path.boundingRect().y() + path.boundingRect().height();
      if (p->bounding_stack.top().y_min > path.boundingRect().y())
        p->bounding_stack.top().y_min = path.boundingRect().y();
      if (p->bounding_stack.top().y_max < p->bounding_stack.top().y_min)
        {
          double tmp = p->bounding_stack.top().y_max;
          p->bounding_stack.top().y_max = p->bounding_stack.top().y_min;
          p->bounding_stack.top().y_min = tmp;
        }
    }
#endif
  p->painter->restore();
}

static void draw_lines(int n, double *px, double *py, int *attributes)
{
  int i, j = 0, rgba, line_color = MAX_COLOR;
  double x, y, xim1, yim1, xi, yi;
  float line_width, red, green, blue;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);

  WC_to_NDC(px[0], py[0], gkss->cntnr, x, y);
  seg_xform(&x, &y);
  NDC_to_DC(x, y, xi, yi);

  for (i = 1; i < n; i++)
    {
      xim1 = xi;
      yim1 = yi;
      WC_to_NDC(px[i], py[i], gkss->cntnr, x, y);
      seg_xform(&x, &y);
      NDC_to_DC(x, y, xi, yi);

      line_width = 0.001 * attributes[j++];
      rgba = attributes[j++];
      red = rgba & 0xff;
      green = (rgba >> 8) & 0xff;
      blue = (rgba >> 16) & 0xff;
      p->rgb[line_color].setRgb(red, green, blue);
      p->rgb[line_color].setAlpha(p->transparency);

      p->painter->setPen(QPen(p->rgb[line_color], line_width * p->nominal_size, Qt::SolidLine, Qt::RoundCap));
      p->painter->drawLine(xim1, yim1, xi, yi);
    }

  p->painter->restore();
}

static void draw_markers(int n, double *px, double *py, int *attributes)
{
  int mk_type, mk_color = MAX_COLOR;
  double x, y, mk_size;
  double *clrt = gkss->viewport[gkss->cntnr];
  int i, j = 0, rgba, red, green, blue, draw;

  mk_type = gkss->asf[3] ? gkss->mtype : gkss->mindex;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);

  for (i = 0; i < n; i++)
    {
      WC_to_NDC(px[i], py[i], gkss->cntnr, x, y);
      seg_xform(&x, &y);

      if (gkss->clip == GKS_K_CLIP)
        draw = (x >= clrt[0] && x <= clrt[1] && y >= clrt[2] && y <= clrt[3]);
      else
        draw = 1;

      mk_size = 0.001 * attributes[j++];
      rgba = attributes[j++];
      red = rgba & 0xff;
      green = (rgba >> 8) & 0xff;
      blue = (rgba >> 16) & 0xff;
      p->rgb[mk_color].setRgb(red, green, blue);
      p->rgb[mk_color].setAlpha(p->transparency);

      if (draw) draw_marker(x, y, mk_type, mk_size, mk_color);
    }

  p->painter->restore();
}

static void draw_triangles(int n, double *px, double *py, int ntri, int *tri)
{
  double x, y, xi, yi;
  int i, j, k, rgba, line_color = MAX_COLOR;
  int red, green, blue;
  QPolygonF *triangle;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);

  if (n > p->max_points)
    {
      p->points->resize(n);
      p->max_points = n;
    }

  for (i = 0; i < n; ++i)
    {
      WC_to_NDC(px[i], py[i], gkss->cntnr, x, y);
      seg_xform(&x, &y);
      NDC_to_DC(x, y, xi, yi);
      (*p->points)[i] = QPointF(xi, yi);
#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
      if (!p->bounding_stack.empty())
        {
          double point_x, point_y;
          point_x = xi;
          point_y = yi;
          if (p->bounding_stack.top().x_max <= point_x) p->bounding_stack.top().x_max = point_x;
          if (p->bounding_stack.top().x_min >= point_x) p->bounding_stack.top().x_min = point_x;
          if (p->bounding_stack.top().y_max <= point_y) p->bounding_stack.top().y_max = point_y;
          if (p->bounding_stack.top().y_min >= point_y) p->bounding_stack.top().y_min = point_y;
        }
#endif
    }

  triangle = new QPolygonF(3);
  j = 0;
  for (i = 0; i < ntri / 4; ++i)
    {
      for (k = 0; k < 3; ++k)
        {
          (*triangle)[k] = (*p->points)[tri[j] - 1];
          j++;
        }

      rgba = tri[j++];
      red = rgba & 0xff;
      green = (rgba >> 8) & 0xff;
      blue = (rgba >> 16) & 0xff;
      p->rgb[line_color].setRgb(red, green, blue);
      p->rgb[line_color].setAlpha(p->transparency);

      p->painter->setPen(
          QPen(p->rgb[line_color], gkss->lwidth * p->nominal_size, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));

      p->painter->drawPolygon(triangle->constData(), 3);
    }
  delete triangle;

  p->painter->restore();
}

static void fill_polygons(int n, double *px, double *py, int nply, int *ply)
{
  double x, y, xi, yi;
  int i, j, k, rgba, len;
  int red, green, blue, alpha;
  QColor fill_color;

  p->painter->save();
  p->painter->setRenderHint(QPainter::Antialiasing);

  QColor border_color(p->rgb[gkss->bcoli]);
  border_color.setAlpha(p->transparency);

  if (n > p->max_points)
    {
      p->points->resize(n);
      p->max_points = n;
    }

  for (i = 0; i < n; ++i)
    {
      WC_to_NDC(px[i], py[i], gkss->cntnr, x, y);
      seg_xform(&x, &y);
      NDC_to_DC(x, y, xi, yi);
      (*p->points)[i] = QPointF(xi, yi);
#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
      if (!p->bounding_stack.empty())
        {
          double point_x, point_y;
          point_x = xi;
          point_y = yi;
          if (p->bounding_stack.top().x_max <= point_x) p->bounding_stack.top().x_max = point_x;
          if (p->bounding_stack.top().x_min >= point_x) p->bounding_stack.top().x_min = point_x;
          if (p->bounding_stack.top().y_max <= point_y) p->bounding_stack.top().y_max = point_y;
          if (p->bounding_stack.top().y_min >= point_y) p->bounding_stack.top().y_min = point_y;
        }
#endif
    }

  j = 0;
  while (j < nply)
    {
      len = ply[j++];
      if (len > p->max_polygon)
        {
          p->polygon->resize(len);
          p->max_polygon = len;
        }
      for (k = 0; k < len; ++k)
        {
          (*p->polygon)[k] = (*p->points)[ply[j] - 1];
          j++;
        }

      rgba = (unsigned int)ply[j++];
      red = rgba & 0xff;
      green = (rgba >> 8) & 0xff;
      blue = (rgba >> 16) & 0xff;
      alpha = (rgba >> 24) & 0xff;
      fill_color.setRgb(red, green, blue);
      fill_color.setAlpha(alpha);

      p->painter->setBrush(QBrush(fill_color, Qt::SolidPattern));
      p->painter->setPen(QPen(border_color, gkss->bwidth * p->nominal_size, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
      p->painter->drawPolygon(p->polygon->constData(), len);
    }

  p->painter->restore();
}

static void gdp(int n, double *px, double *py, int primid, int nc, int *codes)
{
  switch (primid)
    {
    case GKS_K_GDP_DRAW_PATH:
      draw_path(n, px, py, nc, codes);
      break;
    case GKS_K_GDP_DRAW_LINES:
      draw_lines(n, px, py, codes);
      break;
    case GKS_K_GDP_DRAW_MARKERS:
      draw_markers(n, px, py, codes);
      break;
    case GKS_K_GDP_DRAW_TRIANGLES:
      draw_triangles(n, px, py, nc, codes);
      break;
    case GKS_K_GDP_FILL_POLYGONS:
      fill_polygons(n, px, py, nc, codes);
      break;
    default:
      gks_perror("invalid drawing primitive ('%d')", primid);
      exit(1);
    }
}

static void memory_plugin_dl_render(int fctid, int dx, int dy, int dimx, int *ia, int lr1, double *r1, int lr2,
                                    double *r2, int lc, char *chars, void **ptr)
{
  double ratio, w, h;

  switch (fctid)
    {
    case 2:
      ratio = (p->window[1] - p->window[0]) / (p->window[3] - p->window[2]);

      if (p->width > p->height * ratio)
        {
          w = p->height * ratio;
          h = p->height;
        }
      else
        {
          w = p->width;
          h = p->width / ratio;
        }
      if (!p->memory_plugin_initialised)
        {
          int memory_plugin_init_ia[3];
          if (!p->prevent_resize_by_dl)
            {
              p->window[0] = p->window[2] = 0.0;
              p->window[1] = p->window[3] = 1.0;
            }

          p->memory_plugin_mem_path = (char *)gks_malloc(1024);
          p->memory_plugin_mem_ptr = (int *)gks_malloc(3 * sizeof(int) + sizeof(unsigned char *));

          p->memory_plugin_mem_ptr[0] = w;
          p->memory_plugin_mem_ptr[1] = h;
          p->memory_plugin_mem_ptr[2] = p->device_dpi_x * p->device_pixel_ratio;
          *((unsigned char **)(p->memory_plugin_mem_ptr + 3)) = NULL;

          snprintf(p->memory_plugin_mem_path, 1024, "!resizable@%p.mem:r", (void *)p->memory_plugin_mem_ptr);
          chars = p->memory_plugin_mem_path;
          /* set wstype for cairo or agg png in memory */
          memory_plugin_init_ia[2] = p->memory_plugin_wstype;
          p->memory_plugin_initialised = true;
          p->memory_plugin_ws_state_list = *ptr;
          p->memory_plugin(fctid, 0, 0, 3, memory_plugin_init_ia, 0, NULL, 0, NULL, strlen(chars), chars,
                           (&p->memory_plugin_ws_state_list));
          /* activate cairo or agg workstation */
          p->memory_plugin(4, 0, 0, 0, NULL, 0, NULL, 0, NULL, 0, NULL, (&p->memory_plugin_ws_state_list));
        }
      else
        {
          double vp_size[4] = {0};
          /* clear cairo or agg workstation */
          p->memory_plugin(6, 0, 0, 0, NULL, 0, NULL, 0, NULL, 0, NULL, (&p->memory_plugin_ws_state_list));
          /* resize cairo or agg workstation to Qt window */
          vp_size[1] = w * 2.54 / 100 / p->device_dpi_x;
          vp_size[3] = h * 2.54 / 100 / p->device_dpi_y;
          p->memory_plugin(55, 0, 0, 0, NULL, 0, vp_size, 0, vp_size + 2, 0, NULL,
                           (void **)(&p->memory_plugin_ws_state_list));
        }
      return;
    case 54:
      if (!p->prevent_resize_by_dl || !p->interp_was_called)
        {
          p->window[0] = r1[0];
          p->window[1] = r1[1];
          p->window[2] = r2[0];
          p->window[3] = r2[1];
        }
      break;
    case 55:
      if (!p->prevent_resize_by_dl)
        {
          p->viewport[0] = r1[0];
          p->viewport[1] = r1[1];
          p->viewport[2] = r2[0];
          p->viewport[3] = r2[1];
        }
      break;
    case 109:
      p->nominal_size = min(p->width, p->height) / 500.0;
      if (gkss->nominal_size > 0) p->nominal_size *= gkss->nominal_size;
      break;
    }
  if (p->memory_plugin_initialised)
    {
      p->memory_plugin(fctid, dx, dy, dimx, ia, lr1, r1, lr2, r2, lc, chars, (&p->memory_plugin_ws_state_list));
    }
}

static void gks_memory_plugin_write_page()
{
  int width, height;
  unsigned char *mem;
  int ia[2] = {0, GKS_K_WRITE_PAGE_FLAG};
  p->memory_plugin(8, 0, 0, 0, ia, 0, NULL, 0, NULL, 0, NULL, (void **)(&p->memory_plugin_ws_state_list));

  width = p->memory_plugin_mem_ptr[0];
  height = p->memory_plugin_mem_ptr[1];
  mem = *((unsigned char **)(p->memory_plugin_mem_ptr + 3));

  QImage img = QImage(mem, width, height, QImage::Format_ARGB32_Premultiplied);
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
  img.setDevicePixelRatio(p->device_pixel_ratio);
#endif
  width /= p->device_pixel_ratio;
  height /= p->device_pixel_ratio;

  p->painter->drawPixmap((p->width - width) / 2, (p->height - height) / 2, QPixmap::fromImage(img));
}

static void qt_dl_render(int fctid, int dx, int dy, int dimx, int *ia, int lr1, double *r1, int lr2, double *r2, int lc,
                         char *chars, void **ptr)
{
  GKS_UNUSED(lr1);
  GKS_UNUSED(lr2);
  GKS_UNUSED(lc);
  static gks_state_list_t saved_gkss;
  int true_color;
  int cur_id;
  bounding_struct *top;

  switch (fctid)
    {
    case 2:
      memmove(&saved_gkss, gkss, sizeof(gks_state_list_t));
      memmove(gkss, *ptr, sizeof(gks_state_list_t));

      if (!p->prevent_resize_by_dl)
        {
          p->window[0] = p->window[2] = 0.0;
          p->window[1] = p->window[3] = 1.0;
        }

      p->viewport[0] = p->viewport[2] = 0.0;
      p->viewport[1] = p->mwidth;
      p->viewport[3] = p->mheight;

      set_xform();
      init_norm_xform();
      init_colors();

      gkss->fontfile = fontfile;
      gks_init_core(gkss);
      break;

    case 4:
      p->state = GKS_K_WS_ACTIVE;
      break;

    case 5:
      p->state = GKS_K_WS_INACTIVE;
      break;

    case 11:
      break;

    case 12:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          polyline(ia[0], r1, r2);
        }
      break;

    case 13:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          polymarker(ia[0], r1, r2);
        }
      break;

    case 14:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          text(r1[0], r2[0], strlen(chars), chars);
        }
      break;

    case 15:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          fillarea(ia[0], r1, r2);
        }
      break;

    case 16:
    case 201:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          true_color = fctid == DRAW_IMAGE;
          cellarray(r1[0], r1[1], r2[0], r2[1], dx, dy, dimx, ia, true_color);
        }
      break;

    case 17:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          gdp(ia[0], r1, r2, ia[1], ia[2], ia + 3);
        }
      break;

    case 48:
      set_color_rep(ia[1], r1[0], r1[1], r1[2]);
      break;

    case 49:
      set_norm_xform(*ia, gkss->window[*ia], gkss->viewport[*ia]);
      gks_set_norm_xform(*ia, gkss->window[*ia], gkss->viewport[*ia]);
      break;

    case 50:
      set_norm_xform(*ia, gkss->window[*ia], gkss->viewport[*ia]);
      gks_set_norm_xform(*ia, gkss->window[*ia], gkss->viewport[*ia]);

      if (p->state == GKS_K_WS_ACTIVE)
        {
          if (*ia == gkss->cntnr) set_clip_rect(*ia);
        }
      break;

    case 52:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          set_clip_rect(gkss->cntnr);
        }
      break;

    case 53:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          set_clip_rect(gkss->cntnr);
        }
      break;

    case 54:
      if (!p->prevent_resize_by_dl || !p->interp_was_called)
        {
          /*
           * In floating window managers, there is always a paint event before a user-generated resize event. Thus,
           * in floating windows managers the first interpretation of the display list always initializes the
           * wswindow (`p->prevent_resize_by_dl` is `false`). In tiling window managers, the first user-generated
           * resize event is queued before the first paint event (the wm fits the window into a tile). In this case,
           * wswindow would never be set. Therefore, always initialize wswindow if this is the first interpretation
           * of a display list.
           */
          p->window[0] = r1[0];
          p->window[1] = r1[1];
          p->window[2] = r2[0];
          p->window[3] = r2[1];
        }

      set_xform();
      init_norm_xform();
      break;

    case 55:
      if (!p->prevent_resize_by_dl)
        {
          p->viewport[0] = r1[0];
          p->viewport[1] = r1[1];
          p->viewport[2] = r2[0];
          p->viewport[3] = r2[1];
        }

      if (p->state == GKS_K_WS_ACTIVE)
        {
          resize_window();
        }
      set_xform();
      init_norm_xform();
      break;

    case 109:
      p->nominal_size = min(p->width, p->height) / 500.0;
      if (gkss->nominal_size > 0) p->nominal_size *= gkss->nominal_size;
      break;

    case 203:
      p->transparency = (int)(r1[0] * 255);
      break;

    case BEGIN_SELECTION:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          if (p->selection == NULL)
            {
              p->selection = new QPixmap(p->width * p->device_pixel_ratio, p->height * p->device_pixel_ratio);
#if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
              p->selection->setDevicePixelRatio(p->device_pixel_ratio);
#endif
              p->selection->fill(Qt::white);
            }
          p->painter->assign(*p->selection, *p->paint_device);
        }
      break;

    case END_SELECTION:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          p->painter->assign(*p->pixmap, *p->paint_device);
        }
      break;

    case MOVE_SELECTION:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          if (p->selection != NULL)
            {
              int x_offset = (int)(p->a * r1[0] + 0.5);
              int y_offset = (int)(p->c * r2[0] + 0.5);
              QPainter::CompositionMode lastMode = p->painter->compositionMode();
              p->painter->drawPixmap(QPoint(0, 0), *p->pixmap);
              p->painter->setCompositionMode(QPainter::RasterOp_NotSourceXorDestination);
              p->painter->drawPixmap(QPoint(x_offset, y_offset), *p->selection);
              p->painter->setCompositionMode(lastMode);
            }
        }
      break;

#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
    case GKS_SET_BBOX_CALLBACK: /* 260 */
      if (p->state == GKS_K_WS_ACTIVE)
        {
          cur_id = ia[0];
#ifdef _WIN32
          p->bounding_stack.push(
              {DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX, (void (*)(int, double, double, double, double))r1, cur_id});
#else
          p->bounding_stack.push((bounding_struct){DBL_MAX, -DBL_MAX, DBL_MAX, -DBL_MAX,
                                                   (void (*)(int, double, double, double, double))r1, cur_id});
#endif
          p->mask_callback = (void (*)(unsigned int, unsigned int, unsigned int *))r2;
          if (p->mask_callback != NULL) p->painter->beginGroup(cur_id);
        }
      break;

    case GKS_CANCEL_BBOX_CALLBACK: /* 261 */
      if (p->state == GKS_K_WS_ACTIVE)
        {
          if (p->mask_callback != NULL) p->painter->endGroup();
          assert(!p->bounding_stack.empty());
          top = &p->bounding_stack.top();
          top->fun_call(top->item_id, top->x_min, top->x_max, top->y_min, top->y_max);
          p->bounding_stack.pop();
        }
      break;
#endif

    case SET_BACKGROUND:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          if (p->pixmap)
            {
              p->painter->freezeBackground(r1[0], r1[1], r2[0], r2[1]);
            }
        }
      break;

    case CLEAR_BACKGROUND:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          p->painter->clearBackground();
        }
      break;

    case GKS_BEGIN_PARTIAL:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          cur_id = ia[0];
          p->partial_drawing_callback =
              (void (*)(int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int *))r1;
          p->painter->beginPartial(cur_id);
        }
      break;

    case GKS_END_PARTIAL:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          cur_id = ia[0];
          p->painter->endPartial(cur_id);
        }
      break;
    }
}

static void dl_render_function(int fctid, int dx, int dy, int dimx, int *ia, int lr1, double *r1, int lr2, double *r2,
                               int lc, char *chars, void **ptr)
{
  if (fctid == 2)
    {
      if (ia[2] == 412)
        {
          p->memory_plugin_wstype = 143;
          p->memory_plugin = gks_cairo_plugin;
        }
      else if (ia[2] == 413)
        {
          p->memory_plugin_wstype = 173;
          p->memory_plugin = gks_agg_plugin;
        }
      else
        {
          p->memory_plugin_wstype = 0;
        }
    }
  if (p->memory_plugin_wstype)
    {
      memory_plugin_dl_render(fctid, dx, dy, dimx, ia, lr1, r1, lr2, r2, lc, chars, ptr);
    }
  else
    {
      qt_dl_render(fctid, dx, dy, dimx, ia, lr1, r1, lr2, r2, lc, chars, ptr);
    }
}

static bool dl_contains_only_background_fctid(const char *s)
{
  int sp = 0, *len = NULL, *fctid = NULL;
  // see `purge` in `dl.c` for preserved fctids (+ `2` for openws)
  const std::vector<int> leading_ignored_fctids = {2, 48, 54, 55};

  while (true)
    {
      RESOLVE(len, int, sizeof(int));
      if (*len == 0) break;
      RESOLVE(fctid, int, sizeof(int));
      if (std::find(leading_ignored_fctids.begin(), leading_ignored_fctids.end(), *fctid) ==
          leading_ignored_fctids.end())
        break;
      sp += *len - 2 * sizeof(int);
    }

  return *fctid == CLEAR_BACKGROUND || *fctid == SET_BACKGROUND;
}

static bool read_wswindow_from_dl(const char *s, double *wswindow)
{
  int sp = 0, *len = NULL, *fctid = NULL;
  bool found_wswindow = false;

  while (true)
    {
      RESOLVE(len, int, sizeof(int));
      if (*len == 0) break;
      RESOLVE(fctid, int, sizeof(int));
      if (*fctid == 54)
        {
          double *wswindow_ptr = (double *)(s + sp + sizeof(int)); // `+ sizeof(int)` skips the integer array `ia`
          memcpy(wswindow, wswindow_ptr, 4 * sizeof(double));
          found_wswindow = true;
        }
      sp += *len - 2 * sizeof(int);
    }

  return found_wswindow;
}

static void interp(char *str)
{
  char *s;
  int sp = 0, *len = NULL;

  s = str;

  p->partial_draw = gks_dl_has_one_of_item(s, 1, GKS_BEGIN_PARTIAL);
  if (!p->partial_draw && !dl_contains_only_background_fctid(s))
    {
      p->painter->clear();
      if (p->painter->hasBackground()) p->painter->drawBackground();
    }

  p->state = GKS_K_WS_ACTIVE; // We always have to start in active state

  RESOLVE(len, int, sizeof(int));
  while (*len)
    {
      sp += gks_dl_read_item(s + sp, &gkss, dl_render_function);
      RESOLVE(len, int, sizeof(int));
    }

  if (p->memory_plugin_wstype && p->memory_plugin_initialised)
    {
      gks_memory_plugin_write_page();
    }

  if (!p->partial_draw && p->mask_callback != NULL)
    {
      const auto &group_mask = *p->painter->groupMask();
      p->mask_callback(group_mask.width(), group_mask.height(), group_mask.toCMask());
    }

  if (p->partial_drawing_callback != NULL)
    {
      for (const auto &id_region_pair : p->painter->partialCRegions())
        {
          const auto &id = id_region_pair.first;
          const auto &region = id_region_pair.second;
          p->partial_drawing_callback(id, region.x, region.y, region.w, region.h, region.pixels);
        }
    }

  p->interp_was_called = true;
}

static void initialize_data()
{
  int i;

  p->pixmap = p->selection = NULL;
  p->font = new QFont();

  p->points = new QPolygonF(MAX_POINTS);
  p->npoints = 0;
  p->max_points = MAX_POINTS;

  p->polygon = new QPolygonF(MAX_POLYGON);
  p->max_polygon = MAX_POLYGON;

  for (i = 0; i < PATTERNS; i++)
    {
      p->pattern[i] = NULL;
      p->pcolor[i] = -1;
    }

  p->empty = true;

  p->memory_plugin_initialised = false;
  p->prevent_resize_by_dl = false;
  p->window_stays_on_top = false;
  p->interp_was_called = false;
  p->partial_draw = false;

  p->window[0] = 0.0;
  p->window[1] = 1.0;
  p->window[2] = 0.0;
  p->window[3] = 1.0;

  p->transparency = 255;

  p->mask_callback = NULL;
  p->partial_drawing_callback = NULL;
}

static void release_data()
{
  int i;

  for (i = 0; i < PATTERNS; i++)
    if (p->pattern[i] != NULL) free(p->pattern[i]);

  delete p->polygon;
  delete p->points;
  delete p->font;
#ifndef QT_PLUGIN_USED_AS_PLUGIN_CODE
  /* The pixmap is only owned if the code is not compiled as plugin. In plugin mode, the pixmap is only a non-owning
   * pointer to the underlying paint device of the painter object. */
  if (p->pixmap) delete p->pixmap;
#endif
  delete p;
}

static void update_metrics(const QPaintDevice *device)
{
  p->width = device->width();
  p->height = device->height();
  if (p->has_user_defined_device_pixel_ratio)
    {
      /* This case was introduced to work around broken HiDPI support in `QQuickPaintedItem`. QML uses phyiscal instead
       * of logical pixels, but this plugin works with logical pixels. Therefore, convert the physical `width` and
       * `height` to logical pixels by dividing by `p->device_pixel_ratio` given by the user. Multiply with the
       * `device_pixel_ratio` set on the paint device to support logical pixels in QML in the future. */
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
      p->width *= device->devicePixelRatioF() / p->device_pixel_ratio;
      p->height *= device->devicePixelRatioF() / p->device_pixel_ratio;
#elif QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
      p->width *= device->devicePixelRatio() / p->device_pixel_ratio;
      p->height *= device->devicePixelRatio() / p->device_pixel_ratio;
#else
      p->width /= p->device_pixel_ratio;
      p->height /= p->device_pixel_ratio;
#endif
    }
  else
    {
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
      p->device_pixel_ratio = device->devicePixelRatioF();
#elif QT_VERSION >= QT_VERSION_CHECK(5, 1, 0)
      p->device_pixel_ratio = device->devicePixelRatio();
#else
      p->device_pixel_ratio = 1.0;
#endif
    }
  p->device_dpi_x = device->physicalDpiX();
  p->device_dpi_y = device->physicalDpiY();
  p->mwidth = (double)p->width / p->device_dpi_x * 0.0254;
  p->mheight = (double)p->height / p->device_dpi_y * 0.0254;
  p->nominal_size = min(p->width, p->height) / 500.0;
  if (gkss->nominal_size > 0) p->nominal_size *= gkss->nominal_size;
}

static int get_paint_device(void)
{
  char *env;

  env = (char *)gks_getenv("GKS_CONID");
  if (!env) env = (char *)gks_getenv("GKSconid");

  if (env != NULL)
    {
      bool has_exclamation_mark = strchr(env, '!');
      bool has_hash_mark = strchr(env, '#');
      QPainter *painter;
      p->has_user_defined_device_pixel_ratio = has_hash_mark;
      if (has_exclamation_mark && has_hash_mark)
        {
          QWidget *widget; // Use a temporary QWidget pointer since QWidget inherits from multiple base classes
          sscanf(env, "%p!%p#%lf", (void **)&widget, (void **)&painter, &p->device_pixel_ratio);
          p->paint_device = static_cast<QPaintDevice *>(widget);
        }
      else if (has_exclamation_mark)
        {
          QWidget *widget; // Use a temporary QWidget pointer since QWidget inherits from multiple base classes
          sscanf(env, "%p!%p", (void **)&widget, (void **)&painter);
          p->paint_device = static_cast<QPaintDevice *>(widget);
        }
      else if (has_hash_mark)
        {
          sscanf(env, "%p#%lf", (void **)&painter, &p->device_pixel_ratio);
          p->paint_device = painter->device();
        }
      else
        {
          sscanf(env, "%p", (void **)&painter);
          p->paint_device = painter->device();
        }
#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
      QPixmap *pixmap = dynamic_cast<QPixmap *>(painter->device());
      if (pixmap != NULL)
        {
          p->pixmap = pixmap;
        }
#endif
      if (!p->painter)
        {
          p->painter = std::unique_ptr<ProxyPainter>(new ProxyPainter(*painter, *p->paint_device));
        }
      else
        {
          p->painter->assign(*painter, *p->paint_device);
        }
    }
  else
    {
      return 1;
    }

  update_metrics(p->paint_device);

  return 0;
}

static void inqdspsize(double *mwidth, double *mheight, int *width, int *height)
{
#if QT_VERSION >= 0x050000
  QScreen *screen = QGuiApplication::primaryScreen();
  if (screen)
    {
      *mwidth = screen->physicalSize().width() * 0.001;
      *mheight = screen->physicalSize().height() * 0.001;
      *width = screen->size().width();
      *height = screen->size().height();
    }
  else
    {
      *mwidth = 0;
      *mheight = 0;
      *width = 0;
      *height = 0;
    }
#else
  {
    QWidget *screen = QApplication::desktop()->screen();
    *mwidth = screen->widthMM() * 0.001;
    *mheight = screen->heightMM() * 0.001;
    *width = screen->width();
    *height = screen->height();
  }
#endif
}

#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
void QT_PLUGIN_ENTRY_NAME(int fctid, int dx, int dy, int dimx, int *i_arr, int len_f_arr_1, double *f_arr_1,
                          int len_f_arr_2, double *f_arr_2, int len_c_arr, char *c_arr, void **ptr)
{
  double aspect_ratio;

  p = (ws_state_list *)*ptr;

  switch (fctid)
    {
    case 2:
      gkss = (gks_state_list_t *)*ptr;
      p = new ws_state_list;

      p->width = p->height = 500;
      p->nominal_size = 1;

      initialize_data();

      if (get_paint_device() == 0)
        {
          f_arr_1[0] = p->mwidth;
          f_arr_2[0] = p->mheight;
          i_arr[0] = p->width;
          i_arr[1] = p->height;
        }
      else
        {
          inqdspsize(&f_arr_1[0], &f_arr_2[0], &i_arr[0], &i_arr[1]);
        }

      *ptr = p;
      break;

    case 3:
      if (fontfile > 0)
        {
          gks_close_font(fontfile);
          gkss->fontfile = fontfile = 0;
        }
      release_data();

      p = NULL;
      break;

    case 4:
      p->state = GKS_K_WS_ACTIVE;
      break;

    case 5:
      p->state = GKS_K_WS_INACTIVE;
      break;

    case 8:
      if (p->state == GKS_K_WS_ACTIVE)
        {
          if (i_arr[1] & GKS_K_PERFORM_FLAG)
            {
              if (get_paint_device() == 0)
                interp(p->dl.buffer);
              else if (!p->empty)
                gks_perror("can't obtain Qt drawable");
            }
        }
      break;

    case 11:  /* escape */
    case 12:  /* polyline */
    case 13:  /* polymarker */
    case 14:  /* text */
    case 15:  /* fill area */
    case 16:  /* cell array */
    case 201: /* draw image */
      p->empty = false;
      break;

    case 205: /* configure ws */
      update_metrics(p->paint_device);
      f_arr_1[0] = p->mwidth;
      f_arr_2[0] = p->mheight;
      i_arr[0] = p->width;
      i_arr[1] = p->height;
      return;

    case 209: /* inq_ws_state */
      {
        double wswindow[4];
        if (!read_wswindow_from_dl(p->dl.buffer, wswindow))
          {
            memcpy(wswindow, p->window, 4 * sizeof(double));
          }
        aspect_ratio =
            (wswindow[1] - wswindow[0]) / (wswindow[3] - wswindow[2]) * (1.0 * p->device_dpi_x / p->device_dpi_y);
      }
      get_paint_device();
      if (p->width > p->height * aspect_ratio)
        {
          i_arr[0] = nint(p->height * aspect_ratio);
          i_arr[1] = p->height;
        }
      else
        {
          i_arr[0] = p->width;
          i_arr[1] = nint(p->width / aspect_ratio);
        }
      f_arr_1[0] = p->device_pixel_ratio;
      return;

    default:;
    }

  if (p != NULL)
    gks_dl_write_item(&p->dl, fctid, dx, dy, dimx, i_arr, len_f_arr_1, f_arr_1, len_f_arr_2, f_arr_2, len_c_arr, c_arr,
                      gkss);
}
#endif

#else

#ifdef QT_PLUGIN_USED_AS_PLUGIN_CODE
#define QT_NAME_STRING "Qt"

void QT_PLUGIN_ENTRY_NAME(int fctid, int dx, int dy, int dimx, int *i_arr, int len_f_arr_1, double *f_arr_1,
                          int len_f_arr_2, double *f_arr_2, int len_c_arr, char *c_arr, void **ptr)
{
  GKS_UNUSED(dx);
  GKS_UNUSED(dy);
  GKS_UNUSED(dimx);
  GKS_UNUSED(i_arr);
  GKS_UNUSED(len_f_arr_1);
  GKS_UNUSED(f_arr_1);
  GKS_UNUSED(len_f_arr_2);
  GKS_UNUSED(f_arr_2);
  GKS_UNUSED(len_c_arr);
  GKS_UNUSED(c_arr);
  GKS_UNUSED(ptr);

  if (fctid == 2)
    {
      gks_perror(QT_NAME_STRING " support not compiled in");
      i_arr[0] = 0;
      f_arr_1[0] = 0;
      f_arr_2[0] = 0;
      i_arr[0] = 0;
      i_arr[1] = 0;
      if (c_arr != nullptr) c_arr[0] = '\0';
    }
}

#endif

#endif
