//========================================================================
//
// ArthurOutputDev.cc
//
// Copyright 2003 Glyph & Cog, LLC
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2005 Brad Hards <bradh@frogmouth.net>
// Copyright (C) 2005-2009, 2011, 2012, 2014, 2015, 2018, 2019 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2008, 2010 Pino Toscano <pino@kde.org>
// Copyright (C) 2009, 2011 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2009 Petr Gajdos <pgajdos@novell.com>
// Copyright (C) 2010 Matthias Fauconneau <matthias.fauconneau@gmail.com>
// Copyright (C) 2011 Andreas Hartmetz <ahartmetz@gmail.com>
// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org>
// Copyright (C) 2013 Mihai Niculescu <q.quark@gmail.com>
// Copyright (C) 2017, 2018, 2020 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include <config.h>

#include <cstring>
#include <cmath>

#include <array>

#include "goo/gfile.h"
#include "GlobalParams.h"
#include "Error.h"
#include "Object.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "Link.h"
#include "FontEncodingTables.h"
#include <fofi/FoFiTrueType.h>
#include <fofi/FoFiType1C.h>
#include "ArthurOutputDev.h"
#include "Page.h"
#include "Gfx.h"
#include "PDFDoc.h"

#include <QtCore/QtDebug>
#include <QRawFont>
#include <QGlyphRun>
#include <QtGui/QPainterPath>
#include <QPicture>


class ArthurType3Font
{
public:

  ArthurType3Font(PDFDoc* doc, Gfx8BitFont* font);

  const QPicture& getGlyph(int gid) const;

private:
  PDFDoc* m_doc;
  Gfx8BitFont* m_font;

  mutable std::vector<std::unique_ptr<QPicture> > glyphs;

public:
  std::vector<int> codeToGID;
};

ArthurType3Font::ArthurType3Font(PDFDoc* doc, Gfx8BitFont* font)
: m_doc(doc), m_font(font)
{
  char *name;
  const Dict* charProcs = font->getCharProcs();

  // Storage for the rendered glyphs
  glyphs.resize(charProcs->getLength());

  // Compute the code-to-GID map
  char **enc = font->getEncoding();

  codeToGID.resize(256);

  for (int i = 0; i < 256; ++i) {
    codeToGID[i] = 0;
    if (charProcs && (name = enc[i])) {
      for (int j = 0; j < charProcs->getLength(); j++) {
        if (strcmp(name, charProcs->getKey(j)) == 0) {
          codeToGID[i] = j;
        }
      }
    }
  }
}

const QPicture& ArthurType3Font::getGlyph(int gid) const
{
  if (!glyphs[gid]) {

    // Glyph has not been rendered before: render it now

    // Smallest box that contains all the glyphs from this font
    const double* fontBBox = m_font->getFontBBox();
    PDFRectangle box(fontBBox[0], fontBBox[1], fontBBox[2], fontBBox[3]);

    Dict* resDict = m_font->getResources();

    QPainter glyphPainter;
    glyphs[gid] = std::make_unique<QPicture>();
    glyphPainter.begin(glyphs[gid].get());
    auto output_dev = std::make_unique<ArthurOutputDev>(&glyphPainter);

    auto gfx = std::make_unique<Gfx>(
	  m_doc, output_dev.get(), resDict,
	  &box,  // pagebox
	  nullptr  // cropBox
    );

    output_dev->startDoc(m_doc);

    output_dev->startPage (1, gfx->getState(), gfx->getXRef());

    const Dict* charProcs = m_font->getCharProcs();
    Object charProc = charProcs->getVal(gid);
    gfx->display(&charProc);

    glyphPainter.end();
  }

  return *glyphs[gid];
}


//------------------------------------------------------------------------
// ArthurOutputDev
//------------------------------------------------------------------------

ArthurOutputDev::ArthurOutputDev(QPainter *painter):
  m_lastTransparencyGroupPicture(nullptr),
  m_hintingPreference(QFont::PreferDefaultHinting)
{
  m_painter.push(painter);
  m_currentBrush = QBrush(Qt::SolidPattern);

  auto error = FT_Init_FreeType(&m_ftLibrary);
  if (error)
  {
    qCritical() << "An error occurred will initializing the FreeType library";
  }

  // as of FT 2.1.8, CID fonts are indexed by CID instead of GID
  FT_Int major, minor, patch;
  FT_Library_Version(m_ftLibrary, &major, &minor, &patch);
  m_useCIDs = major > 2 ||
              (major == 2 && (minor > 1 || (minor == 1 && patch > 7)));
}

ArthurOutputDev::~ArthurOutputDev()
{
  for (auto& codeToGID : m_codeToGIDCache) {
    gfree(const_cast<int*>(codeToGID.second));
  }

  FT_Done_FreeType(m_ftLibrary);
}

void ArthurOutputDev::startDoc(PDFDoc* doc) {
  xref = doc->getXRef();
  m_doc = doc;

  for (auto& codeToGID : m_codeToGIDCache) {
    gfree(const_cast<int*>(codeToGID.second));
  }
  m_codeToGIDCache.clear();
}

void ArthurOutputDev::startPage(int pageNum, GfxState *state, XRef *)
{}

void ArthurOutputDev::endPage() {
}

void ArthurOutputDev::saveState(GfxState *state)
{
  m_currentPenStack.push(m_currentPen);
  m_currentBrushStack.push(m_currentBrush);
  m_rawFontStack.push(m_rawFont);
  m_type3FontStack.push(m_currentType3Font);
  m_codeToGIDStack.push(m_codeToGID);

  m_painter.top()->save();
}

void ArthurOutputDev::restoreState(GfxState *state)
{
  m_painter.top()->restore();

  m_codeToGID = m_codeToGIDStack.top();
  m_codeToGIDStack.pop();
  m_rawFont = m_rawFontStack.top();
  m_rawFontStack.pop();
  m_currentType3Font = m_type3FontStack.top();
  m_type3FontStack.pop();
  m_currentBrush = m_currentBrushStack.top();
  m_currentBrushStack.pop();
  m_currentPen = m_currentPenStack.top();
  m_currentPenStack.pop();
}

void ArthurOutputDev::updateAll(GfxState *state)
{
  OutputDev::updateAll(state);
  m_needFontUpdate = true;
}

// Set CTM (Current Transformation Matrix) to a fixed matrix
void ArthurOutputDev::setDefaultCTM(const double *ctm)
{
  m_painter.top()->setTransform(QTransform(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]));
}

// Update the CTM (Current Transformation Matrix), i.e., compose the old
// CTM with a new matrix.
void ArthurOutputDev::updateCTM(GfxState *state, double m11, double m12,
				double m21, double m22,
				double m31, double m32)
{
  updateLineDash(state);
  updateLineJoin(state);
  updateLineCap(state);
  updateLineWidth(state);

  QTransform update(m11, m12, m21, m22, m31, m32);

  // We could also set (rather than update) the painter transformation to state->getCMT();
  m_painter.top()->setTransform(update, true);
}

void ArthurOutputDev::updateLineDash(GfxState *state)
{
  double *dashPattern;
  int dashLength;
  double dashStart;
  state->getLineDash(&dashPattern, &dashLength, &dashStart);

  // Special handling for zero-length patterns, i.e., solid lines.
  // Simply calling QPen::setDashPattern with an empty pattern does *not*
  // result in a solid line.  Rather, the current pattern is unchanged.
  // See the implementation of the setDashPattern method in the file qpen.cpp.
  if (dashLength==0)
  {
    m_currentPen.setStyle(Qt::SolidLine);
    m_painter.top()->setPen(m_currentPen);
    return;
  }

  QVector<qreal> pattern(dashLength);
  double scaling = state->getLineWidth();

  //  Negative line widths are not allowed, width 0 counts as 'one pixel width'.
  if (scaling <= 0) {
    scaling = 1.0;
  }

  for (int i = 0; i < dashLength; ++i) {
    // pdf measures the dash pattern in dots, but Qt uses the
    // line width as the unit.
    pattern[i] = dashPattern[i] / scaling;
  }
  m_currentPen.setDashPattern(pattern);
  m_currentPen.setDashOffset(dashStart);
  m_painter.top()->setPen(m_currentPen);
}

void ArthurOutputDev::updateFlatness(GfxState *state)
{
  // qDebug() << "updateFlatness";
}

void ArthurOutputDev::updateLineJoin(GfxState *state)
{
  switch (state->getLineJoin()) {
  case 0:
    // The correct style here is Qt::SvgMiterJoin, *not* Qt::MiterJoin.
    // The two differ in what to do if the miter limit is exceeded.
    // See https://bugs.freedesktop.org/show_bug.cgi?id=102356
    m_currentPen.setJoinStyle(Qt::SvgMiterJoin);
    break;
  case 1:
    m_currentPen.setJoinStyle(Qt::RoundJoin);
    break;
  case 2:
    m_currentPen.setJoinStyle(Qt::BevelJoin);
    break;
  }
  m_painter.top()->setPen(m_currentPen);
}

void ArthurOutputDev::updateLineCap(GfxState *state)
{
  switch (state->getLineCap()) {
  case 0:
    m_currentPen.setCapStyle(Qt::FlatCap);
    break;
  case 1:
    m_currentPen.setCapStyle(Qt::RoundCap);
    break;
  case 2:
    m_currentPen.setCapStyle(Qt::SquareCap);
    break;
  }
  m_painter.top()->setPen(m_currentPen);
}

void ArthurOutputDev::updateMiterLimit(GfxState *state)
{
  m_currentPen.setMiterLimit(state->getMiterLimit());
  m_painter.top()->setPen(m_currentPen);
}

void ArthurOutputDev::updateLineWidth(GfxState *state)
{
  m_currentPen.setWidthF(state->getLineWidth());
  m_painter.top()->setPen(m_currentPen);
  // The updateLineDash method needs to know the line width, but it is sometimes
  // called before the updateLineWidth method.  To make sure that the last call
  // to updateLineDash before a drawing operation is always with the correct line
  // width, we call it here, right after a change to the line width.
  updateLineDash(state);
}

void ArthurOutputDev::updateFillColor(GfxState *state)
{
  GfxRGB rgb;
  QColor brushColour = m_currentBrush.color();
  state->getFillRGB(&rgb);
  brushColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), brushColour.alphaF());
  m_currentBrush.setColor(brushColour);
}

void ArthurOutputDev::updateStrokeColor(GfxState *state)
{
  GfxRGB rgb;
  QColor penColour = m_currentPen.color();
  state->getStrokeRGB(&rgb);
  penColour.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), penColour.alphaF());
  m_currentPen.setColor(penColour);
  m_painter.top()->setPen(m_currentPen);
}

void ArthurOutputDev::updateBlendMode(GfxState * state)
{
  GfxBlendMode blendMode = state->getBlendMode();

  // missing composition modes in QPainter:
  // - CompositionMode_Hue
  // - CompositionMode_Color
  // - CompositionMode_Luminosity
  // - CompositionMode_Saturation

  switch(blendMode){
  case gfxBlendMultiply:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_Multiply);
    break;
  case gfxBlendScreen:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_Screen);
    break;
  case gfxBlendDarken:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_Darken);
    break;
  case gfxBlendLighten:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_Lighten);
    break;
  case gfxBlendColorDodge:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorDodge);
    break;
  case gfxBlendColorBurn:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_ColorBurn);
    break;
  case gfxBlendHardLight:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_HardLight);
    break;
  case gfxBlendSoftLight:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_SoftLight);
    break;
  case gfxBlendDifference:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_Difference);
    break;
  case gfxBlendExclusion:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_Exclusion);
    break;
  case gfxBlendColor:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_Plus);
    break;
  default:
    qDebug() << "Unsupported blend mode, falling back to CompositionMode_SourceOver";
  case gfxBlendNormal:
    m_painter.top()->setCompositionMode(QPainter::CompositionMode_SourceOver);
    break;
  }
}

void ArthurOutputDev::updateFillOpacity(GfxState *state)
{
  QColor brushColour= m_currentBrush.color();
  brushColour.setAlphaF(state->getFillOpacity());
  m_currentBrush.setColor(brushColour);
}

void ArthurOutputDev::updateStrokeOpacity(GfxState *state)
{
  QColor penColour= m_currentPen.color();
  penColour.setAlphaF(state->getStrokeOpacity());
  m_currentPen.setColor(penColour);
  m_painter.top()->setPen(m_currentPen);
}

void ArthurOutputDev::updateFont(GfxState *state)
{
  GfxFont *gfxFont = state->getFont();
  if (!gfxFont)
  {
    return;
  }

  // The key to look in the font caches
  ArthurFontID fontID = {*gfxFont->getID(), state->getFontSize()};

  // Current font is a type3 font
  if (gfxFont->getType() == fontType3)
  {
    auto cacheEntry = m_type3FontCache.find(fontID);

    if (cacheEntry!=m_type3FontCache.end()) {

      // Take the font from the cache
      m_currentType3Font = cacheEntry->second.get();

    } else {

      m_currentType3Font = new ArthurType3Font(m_doc, (Gfx8BitFont*)gfxFont);
      m_type3FontCache.insert(std::make_pair(fontID,std::unique_ptr<ArthurType3Font>(m_currentType3Font)));

    }

    return;
  }

  // Non-type3: is the font in the cache?
  auto cacheEntry = m_rawFontCache.find(fontID);

  if (cacheEntry!=m_rawFontCache.end()) {

    // Take the font from the cache
    m_rawFont = cacheEntry->second.get();

  } else {

    // New font: load it into the cache
    float fontSize = state->getFontSize();

    std::unique_ptr<GfxFontLoc> fontLoc(gfxFont->locateFont(xref, nullptr));

    if (fontLoc) {
      // load the font from respective location
      switch (fontLoc->locType) {
      case gfxFontLocEmbedded: {// if there is an embedded font, read it to memory
        int fontDataLen;
        const char* fontData = gfxFont->readEmbFontFile(xref, &fontDataLen);

        m_rawFont = new QRawFont(QByteArray(fontData, fontDataLen), fontSize, m_hintingPreference);
        m_rawFontCache.insert(std::make_pair(fontID,std::unique_ptr<QRawFont>(m_rawFont)));

        // Free the font data, it was copied in the QByteArray constructor
        free((char*)fontData);
        break;
      }
      case gfxFontLocExternal:{ // font is in an external font file
        QString fontFile(fontLoc->path->c_str());
        m_rawFont = new QRawFont(fontFile, fontSize, m_hintingPreference);
        m_rawFontCache.insert(std::make_pair(fontID,std::unique_ptr<QRawFont>(m_rawFont)));
        break;
      }
      case gfxFontLocResident:{ // font resides in a PS printer
        qDebug() << "Font Resident Encoding:" << fontLoc->encoding->c_str() << ", not implemented yet!";

      break;
      }
      }// end switch

    } else {
      qDebug() << "Font location not found!";
      return;
    }
  }

  if (!m_rawFont->isValid()) {
    qDebug() << "RawFont is not valid";
  }

  // *****************************************************************************
  //  We have now successfully loaded the font into a QRawFont object.  This
  //  allows us to draw all the glyphs in the font.  However, what is missing is
  //  the charcode-to-glyph-index mapping.  Apparently, Qt does not provide this
  //  information at all.  Therefore, we need to figure it ourselves, using
  //  FoFi and FreeType.
  // *****************************************************************************

  m_needFontUpdate = false;

  GfxFontType fontType = gfxFont->getType();

  // Default: no codeToGID table
  m_codeToGID = nullptr;

  // check the font file cache
  Ref id = *gfxFont->getID();

  auto codeToGIDIt = m_codeToGIDCache.find(id);

  if (codeToGIDIt != m_codeToGIDCache.end()) {

    m_codeToGID = codeToGIDIt->second;

  } else {

    std::unique_ptr<char[], void(*)(char*)> tmpBuf(nullptr, [](char* b){ free(b); });
    int tmpBufLen = 0;

    std::unique_ptr<GfxFontLoc> fontLoc(gfxFont->locateFont(xref, nullptr));
    if (!fontLoc) {
      error(errSyntaxError, -1, "Couldn't find a font for '{0:s}'",
	    gfxFont->getName() ? gfxFont->getName()->c_str()
	                       : "(unnamed)");
      return;
    }

    // embedded font
    if (fontLoc->locType == gfxFontLocEmbedded) {
      // if there is an embedded font, read it to memory
      tmpBuf.reset(gfxFont->readEmbFontFile(xref, &tmpBufLen));
      if (! tmpBuf) {
        return;
      }

    // external font
    } else { // gfxFontLocExternal
      // Hmm, fontType has already been set to gfxFont->getType() above.
      // Can it really assume a different value here?
      fontType = fontLoc->fontType;
    }

    switch (fontType) {
    case fontType1:
    case fontType1C:
    case fontType1COT:
    {
      // Load the font face using FreeType
      const int faceIndex = 0;  // We always load the zero-th face from a font
      FT_Face freeTypeFace;

      if (fontLoc->locType != gfxFontLocEmbedded) {
        if (FT_New_Face(m_ftLibrary, fontLoc->path->c_str(), faceIndex, &freeTypeFace)) {
          error(errSyntaxError, -1, "Couldn't create a FreeType face for '{0:s}'",
                gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
          return;
        }
      } else {
        if (FT_New_Memory_Face(m_ftLibrary, (const FT_Byte *)tmpBuf.get(), tmpBufLen, faceIndex, &freeTypeFace)) {
          error(errSyntaxError, -1, "Couldn't create a FreeType face for '{0:s}'",
                gfxFont->getName() ? gfxFont->getName()->c_str() : "(unnamed)");
          return;
        }
      }

      const char *name;

      int *codeToGID = (int *)gmallocn(256, sizeof(int));
      for (int i = 0; i < 256; ++i) {
        codeToGID[i] = 0;
        if ((name = ((const char **)((Gfx8BitFont *)gfxFont)->getEncoding())[i])) {
          codeToGID[i] = (int)FT_Get_Name_Index(freeTypeFace, (char *)name);
          if (codeToGID[i] == 0) {
            name = GfxFont::getAlternateName(name);
            if (name) {
              codeToGID[i] = FT_Get_Name_Index(freeTypeFace, (char *)name);
	    }
          }
        }
      }

      FT_Done_Face(freeTypeFace);

      m_codeToGIDCache[id] = codeToGID;

      break;
    }
    case fontTrueType:
    case fontTrueTypeOT:
    {
      auto ff = (fontLoc->locType != gfxFontLocEmbedded)
                ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str()))
                : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen));

      m_codeToGIDCache[id] = (ff) ? ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff.get()) : nullptr;

      break;
    }
    case fontCIDType0:
    case fontCIDType0C:
    {
      int *cidToGIDMap = nullptr;
      int nCIDs = 0;

      // check for a CFF font
      if (!m_useCIDs) {
        auto ff = (fontLoc->locType != gfxFontLocEmbedded)
                  ? std::unique_ptr<FoFiType1C>(FoFiType1C::load(fontLoc->path->c_str()))
                  : std::unique_ptr<FoFiType1C>(FoFiType1C::make(tmpBuf.get(), tmpBufLen));

        cidToGIDMap = (ff) ? ff->getCIDToGIDMap(&nCIDs) : nullptr;
      }

      m_codeToGIDCache[id] = cidToGIDMap;

      break;
    }
    case fontCIDType0COT:
    {
      int* codeToGID = nullptr;

      if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
        int codeToGIDLen = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
        codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int));
        memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(),
               codeToGIDLen * sizeof(int));
      }

      int *cidToGIDMap = nullptr;
      int nCIDs = 0;

      if (!codeToGID && !m_useCIDs) {
        auto ff = (fontLoc->locType != gfxFontLocEmbedded)
                  ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str()))
                  : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen));

        if (ff && ff->isOpenTypeCFF()) {
          cidToGIDMap = ff->getCIDToGIDMap(&nCIDs);
        }
      }

      m_codeToGIDCache[id] = codeToGID ? codeToGID : cidToGIDMap;

      break;
    }
    case fontCIDType2:
    case fontCIDType2OT:
    {
      int* codeToGID = nullptr;
      int codeToGIDLen = 0;
      if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
        codeToGIDLen = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
        if (codeToGIDLen) {
          codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int));
          memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(),
                 codeToGIDLen * sizeof(int));
        }
      } else {
        auto ff = (fontLoc->locType != gfxFontLocEmbedded)
                  ? std::unique_ptr<FoFiTrueType>(FoFiTrueType::load(fontLoc->path->c_str()))
                  : std::unique_ptr<FoFiTrueType>(FoFiTrueType::make(tmpBuf.get(), tmpBufLen));
        if (! ff) {
          return;
        }
	codeToGID = ((GfxCIDFont *)gfxFont)->getCodeToGIDMap(ff.get(), &codeToGIDLen);
      }

      m_codeToGIDCache[id] = codeToGID;

      break;
    }
    default:
      // this shouldn't happen
      return;
    }

    m_codeToGID = m_codeToGIDCache[id];
  }
}

static QPainterPath convertPath(GfxState *state, const GfxPath *path, Qt::FillRule fillRule)
{
  int i, j;

  QPainterPath qPath;
  qPath.setFillRule(fillRule);
  for (i = 0; i < path->getNumSubpaths(); ++i) {
    const GfxSubpath *subpath = path->getSubpath(i);
    if (subpath->getNumPoints() > 0) {
      qPath.moveTo(subpath->getX(0), subpath->getY(0));
      j = 1;
      while (j < subpath->getNumPoints()) {
	if (subpath->getCurve(j)) {
          qPath.cubicTo( subpath->getX(j),   subpath->getY(j),
                         subpath->getX(j+1), subpath->getY(j+1),
                         subpath->getX(j+2), subpath->getY(j+2));
	  j += 3;
	} else {
	  qPath.lineTo(subpath->getX(j), subpath->getY(j));
	  ++j;
	}
      }
      if (subpath->isClosed()) {
	qPath.closeSubpath();
      }
    }
  }
  return qPath;
}

void ArthurOutputDev::stroke(GfxState *state)
{
  m_painter.top()->strokePath( convertPath( state, state->getPath(), Qt::OddEvenFill ), m_currentPen );
}

void ArthurOutputDev::fill(GfxState *state)
{
  m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::WindingFill ), m_currentBrush );
}

void ArthurOutputDev::eoFill(GfxState *state)
{
  m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::OddEvenFill ), m_currentBrush );
}

bool ArthurOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading, double tMin, double tMax)
{
  double x0, y0, x1, y1;
  shading->getCoords(&x0, &y0, &x1, &y1);

  // get the clip region bbox
  double xMin, yMin, xMax, yMax;
  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);

  // get the function domain
  double t0 = shading->getDomain0();
  double t1 = shading->getDomain1();

  // Max number of splits along the t axis
  constexpr int maxSplits = 256;

  // Max delta allowed in any color component
  const double colorDelta = (dblToCol(1 / 256.0));

  // Number of color space components
  auto nComps = shading->getColorSpace()->getNComps();
  // If the clipping region is a stroke, then the current operation counts as a stroke
  // rather than as a fill, and the opacity has to be set accordingly.
  // See https://gitlab.freedesktop.org/poppler/poppler/-/issues/178
  auto opacity = (state->getStrokePattern()) ? state->getStrokeOpacity() : state->getFillOpacity();

  // Helper function to test two color objects for 'almost-equality'
  auto isSameGfxColor = [&nComps,&colorDelta](const GfxColor &colorA, const GfxColor &colorB)
                        {
                          for (int k = 0; k < nComps; ++k) {
                            if (abs(colorA.c[k] - colorB.c[k]) > colorDelta) {
                              return false;
                            }
                          }
                          return true;
                        };

  // Helper function: project a number into an interval
  // With C++17 this is part of the standard library
  auto clamp = [](double v, double lo, double hi)
                  { return std::min(std::max(v,lo), hi); };

  // ta stores all parameter values where we evaluate the input shading function.
  // In between, QLinearGradient will interpolate linearly.
  // We set up the array with three values.
  std::array<double, maxSplits+1> ta;
  ta[0] = tMin;
  std::array<int, maxSplits+1> next;
  next[0] = maxSplits / 2;
  ta[maxSplits / 2] = 0.5 * (tMin + tMax);
  next[maxSplits / 2] = maxSplits;
  ta[maxSplits] = tMax;

  // compute the color at t = tMin
  double tt = clamp(t0 + (t1 - t0) * tMin, t0, t1);

  GfxColor color0, color1;
  shading->getColor(tt, &color0);

  // Construct a gradient object and set its color at one parameter end
  QLinearGradient gradient(QPointF(x0 + tMin * (x1 - x0), y0 + tMin * (y1 - y0)),
                           QPointF(x0 + tMax * (x1 - x0), y0 + tMax * (y1 - y0)));

  GfxRGB rgb;
  shading->getColorSpace()->getRGB(&color0, &rgb);
  QColor qColor(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b), dblToByte(opacity));
  gradient.setColorAt(0,qColor);

  // Look for more relevant parameter values by bisection
  int i = 0;
  while (i < maxSplits) {

    int j = next[i];
    while (j > i + 1) {

      // Next parameter value to try
      tt = clamp(t0 + (t1 - t0) * ta[j], t0, t1);
      shading->getColor(tt, &color1);

      // j is a good next color stop if the input shading can be approximated well
      // on the interval (ta[i], ta[j]) by a linear interpolation.
      // We test this by comparing the real color in the middle between ta[i] and ta[j]
      // with the linear interpolant there.
      auto midPoint = 0.5 * (ta[i] + ta[j]);
      GfxColor colorAtMidPoint;
      shading->getColor(midPoint, &colorAtMidPoint);

      GfxColor linearlyInterpolatedColor;
      for (int ii=0; ii<nComps; ii++)
        linearlyInterpolatedColor.c[ii] = 0.5 * (color0.c[ii] + color1.c[ii]);

      // If the two colors are equal, ta[j] is a good place for the next color stop; take it!
      if (isSameGfxColor(colorAtMidPoint, linearlyInterpolatedColor))
        break;

      // Otherwise: bisect further
      int k = (i + j) / 2;
      ta[k] = midPoint;
      next[i] = k;
      next[k] = j;
      j = k;
    }

    // set the color
    shading->getColorSpace()->getRGB(&color1, &rgb);
    qColor.setRgb(colToByte(rgb.r), colToByte(rgb.g), colToByte(rgb.b), dblToByte(opacity));
    gradient.setColorAt((ta[j] - tMin)/(tMax - tMin), qColor);

    // Move to the next parameter region
    color0 = color1;
    i = next[i];
  }

  state->moveTo(xMin, yMin);
  state->lineTo(xMin, yMax);
  state->lineTo(xMax, yMax);
  state->lineTo(xMax, yMin);
  state->closePath();

  // Actually paint the shaded region
  QBrush newBrush(gradient);
  m_painter.top()->fillPath( convertPath( state, state->getPath(), Qt::WindingFill ), newBrush );

  state->clearPath();

  // True means: The shaded region has been painted
  return true;
}

void ArthurOutputDev::clip(GfxState *state)
{
  m_painter.top()->setClipPath(convertPath( state, state->getPath(), Qt::WindingFill ), Qt::IntersectClip );
}

void ArthurOutputDev::eoClip(GfxState *state)
{
  m_painter.top()->setClipPath(convertPath( state, state->getPath(), Qt::OddEvenFill ), Qt::IntersectClip );
}

void ArthurOutputDev::clipToStrokePath(GfxState *state)
{
  QPainterPath clipPath = convertPath( state, state->getPath(), Qt::WindingFill );

  // Get the outline of 'clipPath' as a separate path
  QPainterPathStroker stroker;
  stroker.setWidth( state->getLineWidth() );
  stroker.setCapStyle( m_currentPen.capStyle() );
  stroker.setJoinStyle( m_currentPen.joinStyle() );
  stroker.setMiterLimit( state->getMiterLimit() );
  stroker.setDashPattern( m_currentPen.dashPattern() );
  stroker.setDashOffset( m_currentPen.dashOffset() );
  QPainterPath clipPathOutline = stroker.createStroke ( clipPath );

  // The interior of the outline is the desired clipping region
  m_painter.top()->setClipPath(clipPathOutline, Qt::IntersectClip );
}

void ArthurOutputDev::drawChar(GfxState *state, double x, double y,
			       double dx, double dy,
			       double originX, double originY,
			       CharCode code, int nBytes, const Unicode *u, int uLen) {

  // First handle type3 fonts
  GfxFont *gfxFont = state->getFont();

  GfxFontType fontType = gfxFont->getType();
  if (fontType == fontType3) {

    /////////////////////////////////////////////////////////////////////
    //  Draw the QPicture that contains the glyph onto the page
    /////////////////////////////////////////////////////////////////////

    // Store the QPainter state; we need to modify it temporarily
    m_painter.top()->save();

    // Make the glyph position the coordinate origin -- that's our center of scaling
    m_painter.top()->translate(QPointF(x-originX, y-originY));

    const double* mat = gfxFont->getFontMatrix();
    QTransform fontMatrix(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);

    // Scale with the font size
    fontMatrix.scale(state->getFontSize(), state->getFontSize());
    m_painter.top()->setTransform(fontMatrix,true);

    // Apply the text matrix on top
    const double *textMat = state->getTextMat();

    QTransform textTransform(textMat[0] * state->getHorizScaling(),
                             textMat[1] * state->getHorizScaling(),
                             textMat[2],
                             textMat[3],
                             0,
                             0);

    m_painter.top()->setTransform(textTransform,true);

    // Actually draw the glyph
    int gid = m_currentType3Font->codeToGID[code];
    m_painter.top()->drawPicture(QPointF(0,0), m_currentType3Font->getGlyph(gid));

    // Restore transformation
    m_painter.top()->restore();

    return;
  }


  // check for invisible text -- this is used by Acrobat Capture
  int render = state->getRender();
  if (render == 3 || !m_rawFont) {
    qDebug() << "Invisible text found!";
    return;
  }

  if (!(render & 1))
  {
    quint32 glyphIndex = (m_codeToGID) ? m_codeToGID[code] : code;
    QPointF glyphPosition = QPointF(x-originX, y-originY);

    // QGlyphRun objects can hold an entire sequence of glyphs, and it would possibly
    // be more efficient to simply note the glyph and glyph position here and then
    // draw several glyphs at once in the endString method.  What keeps us from doing
    // that is the transformation below: each glyph needs to be drawn upside down,
    // i.e., reflected at its own baseline.  Since we have no guarantee that this
    // baseline is the same for all glyphs in a string we have to do it one by one.
    QGlyphRun glyphRun;
    glyphRun.setRawData(&glyphIndex, &glyphPosition, 1);
    glyphRun.setRawFont(*m_rawFont);

    // Store the QPainter state; we need to modify it temporarily
    m_painter.top()->save();

    // Apply the text matrix to the glyph.  The glyph is not scaled by the font size,
    // because the font in m_rawFont already has the correct size.
    // Additionally, the CTM is upside down, i.e., it contains a negative Y-scaling
    // entry.  Therefore, Qt will paint the glyphs upside down.  We need to temporarily
    // reflect the page at glyphPosition.y().

    // Make the glyph position the coordinate origin -- that's our center of scaling
    const double *textMat = state->getTextMat();

    m_painter.top()->translate(QPointF(glyphPosition.x(),glyphPosition.y()));

    QTransform textTransform(textMat[0] * state->getHorizScaling(),
                             textMat[1] * state->getHorizScaling(),
                             -textMat[2], // reflect at the horizontal axis,
                             -textMat[3], // because CTM is upside-down.
                             0,
                             0);

    m_painter.top()->setTransform(textTransform,true);

    // We are painting a filled glyph here.  But QPainter uses the pen to draw even filled text,
    // not the brush.  (see, e.g.,  http://doc.qt.io/qt-5/qpainter.html#setPen )
    // Therefore we have to temporarily overwrite the pen color.

    // Since we are drawing a filled glyph, one would really expect to have m_currentBrush
    // have the correct color.  However, somehow state->getFillRGB can change without
    // updateFillColor getting called.  Then m_currentBrush may not contain the correct color.
    GfxRGB rgb;
    state->getFillRGB(&rgb);
    QColor fontColor;
    fontColor.setRgbF(colToDbl(rgb.r), colToDbl(rgb.g), colToDbl(rgb.b), state->getFillOpacity());
    m_painter.top()->setPen(fontColor);

    // Actually draw the glyph
    m_painter.top()->drawGlyphRun(QPointF(-glyphPosition.x(),-glyphPosition.y()), glyphRun);

    // Restore transformation and pen color
    m_painter.top()->restore();
  }
}

void ArthurOutputDev::type3D0(GfxState *state, double wx, double wy)
{
}

void ArthurOutputDev::type3D1(GfxState *state, double wx, double wy,
			      double llx, double lly, double urx, double ury)
{
}

void ArthurOutputDev::endTextObject(GfxState *state)
{
}


void ArthurOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
				    int width, int height, bool invert,
				    bool interpolate, bool inlineImg)
{
  auto imgStr = std::make_unique<ImageStream>(
	str, width,
	1,  // numPixelComps
	1  // getBits
  );
  imgStr->reset();

  // TODO: Would using QImage::Format_Mono be more efficient here?
  QImage image(width, height, QImage::Format_ARGB32);
  unsigned int *data = reinterpret_cast<unsigned int *>(image.bits());
  int stride = image.bytesPerLine()/4;

  QRgb fillColor = m_currentBrush.color().rgb();

  for (int y = 0; y < height; y++) {

    unsigned char *pix = imgStr->getLine();

    // Invert the vertical coordinate: y is increasing from top to bottom
    // on the page, but y is increasing bottom to top in the picture.
    unsigned int* dest = data + (height-1-y) * stride;

    for (int x = 0; x < width; x++) {

      bool opaque = ((bool)pix[x]) == invert;
      dest[x] = (opaque) ? fillColor : 0;

    }
  }

  // At this point, the QPainter coordinate transformation (CTM) is such
  // that QRect(0,0,1,1) is exactly the area of the image.
  m_painter.top()->drawImage( QRect(0,0,1,1), image );
  imgStr->close ();
}

//TODO: lots more work here.
void ArthurOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
				int width, int height,
				GfxImageColorMap *colorMap,
				bool interpolate, const int *maskColors, bool inlineImg)
{
  unsigned int *data;
  unsigned int *line;
  int x, y;
  unsigned char *pix;
  int i;
  QImage image;
  int stride;
  
  /* TODO: Do we want to cache these? */
  auto imgStr = std::make_unique<ImageStream>(
	str, width,
	colorMap->getNumPixelComps(),
	colorMap->getBits()
  );
  imgStr->reset();
  
  image = QImage(width, height, QImage::Format_ARGB32);
  data = reinterpret_cast<unsigned int *>(image.bits());
  stride = image.bytesPerLine()/4;
  for (y = 0; y < height; y++) {
    pix = imgStr->getLine();
    // Invert the vertical coordinate: y is increasing from top to bottom
    // on the page, but y is increasing bottom to top in the picture.
    line = data+(height-1-y)*stride;
    colorMap->getRGBLine(pix, line, width);

    if (maskColors) {
      for (x = 0; x < width; x++) {
        for (i = 0; i < colorMap->getNumPixelComps(); ++i) {
            if (pix[i] < maskColors[2*i] * 255||
                pix[i] > maskColors[2*i+1] * 255) {
                *line = *line | 0xff000000;
                break;
            }
        }
        pix += colorMap->getNumPixelComps();
        line++;
      }
    } else {
      for (x = 0; x < width; x++) { *line = *line | 0xff000000; line++; }
    }
  }

  // At this point, the QPainter coordinate transformation (CTM) is such
  // that QRect(0,0,1,1) is exactly the area of the image.
  m_painter.top()->drawImage( QRect(0,0,1,1), image );

}

void ArthurOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
                                          int width, int height,
                                          GfxImageColorMap *colorMap,
                                          bool interpolate,
                                          Stream *maskStr,
                                          int maskWidth, int maskHeight,
                                          GfxImageColorMap *maskColorMap,
                                          bool maskInterpolate)
{
  // Bail out if the image size doesn't match the mask size.  I don't know
  // what to do in this case.
  if (width!=maskWidth || height!=maskHeight)
  {
    qDebug() << "Soft mask size does not match image size!";
    drawImage(state, ref, str, width, height, colorMap, interpolate, nullptr, false);
    return;
  }

  // Bail out if the mask isn't a single channel.  I don't know
  // what to do in this case.
  if (maskColorMap->getColorSpace()->getNComps() != 1)
  {
    qDebug() << "Soft mask is not a single 8-bit channel!";
    drawImage(state, ref, str, width, height, colorMap, interpolate, nullptr, false);
    return;
  }

  /* TODO: Do we want to cache these? */
  auto imgStr = std::make_unique<ImageStream>(
	str, width,
	colorMap->getNumPixelComps(),
	colorMap->getBits()
  );
  imgStr->reset();

  auto maskImageStr = std::make_unique<ImageStream>(
	maskStr, maskWidth,
	maskColorMap->getNumPixelComps(),
	maskColorMap->getBits()
  );
  maskImageStr->reset();

  QImage image(width, height, QImage::Format_ARGB32);
  unsigned int *data = reinterpret_cast<unsigned int *>(image.bits());
  int stride = image.bytesPerLine()/4;

  std::vector<unsigned char> maskLine(maskWidth);

  for (int y = 0; y < height; y++) {

    unsigned char *pix = imgStr->getLine();
    unsigned char *maskPix = maskImageStr->getLine();

    // Invert the vertical coordinate: y is increasing from top to bottom
    // on the page, but y is increasing bottom to top in the picture.
    unsigned int* line = data+(height-1-y)*stride;
    colorMap->getRGBLine(pix, line, width);

    // Apply the mask values to the image alpha channel
    maskColorMap->getGrayLine(maskPix, maskLine.data(), width);
    for (int x = 0; x < width; x++)
    {
      *line = *line | (maskLine[x]<<24);
      line++;
    }
  }

  // At this point, the QPainter coordinate transformation (CTM) is such
  // that QRect(0,0,1,1) is exactly the area of the image.
  m_painter.top()->drawImage( QRect(0,0,1,1), image );
}

void ArthurOutputDev::beginTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/,
                                             GfxColorSpace * /*blendingColorSpace*/,
                                             bool /*isolated*/, bool /*knockout*/,
                                             bool /*forSoftMask*/)
{
  // The entire transparency group will be painted into a
  // freshly created QPicture object.  Since an existing painter
  // cannot change its paint device, we need to construct a
  // new QPainter object as well.
  m_qpictures.push(new QPicture);
  m_painter.push(new QPainter(m_qpictures.top()));
}

void ArthurOutputDev::endTransparencyGroup(GfxState * /*state*/)
{
  // Stop painting into the group
  m_painter.top()->end();

  // Kill the painter that has been used for the transparency group
  delete(m_painter.top());
  m_painter.pop();

  // Store the QPicture object that holds the result of the transparency group
  // painting.  It will be painted and deleted in the method paintTransparencyGroup.
  if (m_lastTransparencyGroupPicture)
  {
    qDebug() << "Found a transparency group that has not been painted";
    delete(m_lastTransparencyGroupPicture);
  }
  m_lastTransparencyGroupPicture = m_qpictures.top();
  m_qpictures.pop();
}

void ArthurOutputDev::paintTransparencyGroup(GfxState * /*state*/, const double * /*bbox*/)
{
  // Actually draw the transparency group
  m_painter.top()->drawPicture(0,0,*m_lastTransparencyGroupPicture);

  // And delete it
  delete(m_lastTransparencyGroupPicture);
  m_lastTransparencyGroupPicture = nullptr;
}

