//========================================================================
//
// GfxState.h
//
// Copyright 1996-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 Kristian Høgsberg <krh@redhat.com>
// Copyright (C) 2006, 2007 Jeff Muizelaar <jeff@infidigm.net>
// Copyright (C) 2006 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2009 Koji Otani <sho@bbr.jp>
// Copyright (C) 2009-2011, 2013, 2016-2021 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
// Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
// Copyright (C) 2011-2014, 2016, 2020 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
// Copyright (C) 2015, 2017, 2020 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2017, 2019 Oliver Sander <oliver.sander@tu-dresden.de>
// Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2020, 2021 Philipp Knechtges <philipp-dev@knechtges.com>
//
// 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
//
//========================================================================

#ifndef GFXSTATE_H
#define GFXSTATE_H

#include "poppler-config.h"
#include "poppler_private_export.h"

#include "Object.h"
#include "Function.h"

#include <cassert>
#include <map>
#include <memory>

class Array;
class Gfx;
class GfxFont;
class PDFRectangle;
class GfxShading;
class OutputDev;
class GfxState;
class GfxResources;
class GfxSeparationColorSpace;

class Matrix
{
public:
    double m[6];

    void init(double xx, double yx, double xy, double yy, double x0, double y0)
    {
        m[0] = xx;
        m[1] = yx;
        m[2] = xy;
        m[3] = yy;
        m[4] = x0;
        m[5] = y0;
    }
    bool invertTo(Matrix *other) const;
    void translate(double tx, double ty);
    void scale(double sx, double sy);
    void transform(double x, double y, double *tx, double *ty) const;
    double determinant() const { return m[0] * m[3] - m[1] * m[2]; }
    double norm() const;
};

//------------------------------------------------------------------------
// GfxBlendMode
//------------------------------------------------------------------------

enum GfxBlendMode
{
    gfxBlendNormal,
    gfxBlendMultiply,
    gfxBlendScreen,
    gfxBlendOverlay,
    gfxBlendDarken,
    gfxBlendLighten,
    gfxBlendColorDodge,
    gfxBlendColorBurn,
    gfxBlendHardLight,
    gfxBlendSoftLight,
    gfxBlendDifference,
    gfxBlendExclusion,
    gfxBlendHue,
    gfxBlendSaturation,
    gfxBlendColor,
    gfxBlendLuminosity
};

//------------------------------------------------------------------------
// GfxColorComp
//------------------------------------------------------------------------

// 16.16 fixed point color component
typedef int GfxColorComp;

#define gfxColorComp1 0x10000

static inline GfxColorComp dblToCol(double x)
{
    return (GfxColorComp)(x * gfxColorComp1);
}

static inline double colToDbl(GfxColorComp x)
{
    return (double)x / (double)gfxColorComp1;
}

static inline unsigned char dblToByte(double x)
{
    return (x * 255.0);
}

static inline double byteToDbl(unsigned char x)
{
    return (double)x / (double)255.0;
}

static inline GfxColorComp byteToCol(unsigned char x)
{
    // (x / 255) << 16  =  (0.0000000100000001... * x) << 16
    //                  =  ((x << 8) + (x) + (x >> 8) + ...) << 16
    //                  =  (x << 8) + (x) + (x >> 7)
    //                                      [for rounding]
    return (GfxColorComp)((x << 8) + x + (x >> 7));
}

static inline unsigned char colToByte(GfxColorComp x)
{
    // 255 * x + 0.5  =  256 * x - x + 0x8000
    return (unsigned char)(((x << 8) - x + 0x8000) >> 16);
}

static inline unsigned short colToShort(GfxColorComp x)
{
    return (unsigned short)(x);
}

//------------------------------------------------------------------------
// GfxColor
//------------------------------------------------------------------------

#define gfxColorMaxComps funcMaxOutputs

struct GfxColor
{
    GfxColorComp c[gfxColorMaxComps];
};

static inline void clearGfxColor(GfxColor *gfxColor)
{
    memset(gfxColor->c, 0, sizeof(GfxColorComp) * gfxColorMaxComps);
}

//------------------------------------------------------------------------
// GfxGray
//------------------------------------------------------------------------

typedef GfxColorComp GfxGray;

//------------------------------------------------------------------------
// GfxRGB
//------------------------------------------------------------------------

struct GfxRGB
{
    GfxColorComp r, g, b;
};

//------------------------------------------------------------------------
// GfxCMYK
//------------------------------------------------------------------------

struct GfxCMYK
{
    GfxColorComp c, m, y, k;
};

//------------------------------------------------------------------------
// GfxColorSpace
//------------------------------------------------------------------------

// NB: The nGfxColorSpaceModes constant and the gfxColorSpaceModeNames
// array defined in GfxState.cc must match this enum.
enum GfxColorSpaceMode
{
    csDeviceGray,
    csCalGray,
    csDeviceRGB,
    csCalRGB,
    csDeviceCMYK,
    csLab,
    csICCBased,
    csIndexed,
    csSeparation,
    csDeviceN,
    csPattern
};

// This shall hold a cmsHPROFILE handle.
// Only use the make_GfxLCMSProfilePtr function to construct this pointer,
// to ensure that the resources are properly released after usage.
typedef std::shared_ptr<void> GfxLCMSProfilePtr;

#ifdef USE_CMS
GfxLCMSProfilePtr POPPLER_PRIVATE_EXPORT make_GfxLCMSProfilePtr(void *profile);
#endif

// wrapper of cmsHTRANSFORM to copy
class GfxColorTransform
{
public:
    void doTransform(void *in, void *out, unsigned int size);
    // transformA should be a cmsHTRANSFORM
    GfxColorTransform(void *transformA, int cmsIntent, unsigned int inputPixelType, unsigned int transformPixelType);
    ~GfxColorTransform();
    GfxColorTransform(const GfxColorTransform &) = delete;
    GfxColorTransform &operator=(const GfxColorTransform &) = delete;
    int getIntent() const { return cmsIntent; }
    int getInputPixelType() const { return inputPixelType; }
    int getTransformPixelType() const { return transformPixelType; }

private:
    GfxColorTransform() { }
    void *transform;
    int cmsIntent;
    unsigned int inputPixelType;
    unsigned int transformPixelType;
};

class POPPLER_PRIVATE_EXPORT GfxColorSpace
{
public:
    GfxColorSpace();
    virtual ~GfxColorSpace();

    GfxColorSpace(const GfxColorSpace &) = delete;
    GfxColorSpace &operator=(const GfxColorSpace &other) = delete;

    virtual GfxColorSpace *copy() const = 0;
    virtual GfxColorSpaceMode getMode() const = 0;

    // Construct a color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(GfxResources *res, Object *csObj, OutputDev *out, GfxState *state, int recursion = 0);

    // Convert to gray, RGB, or CMYK.
    virtual void getGray(const GfxColor *color, GfxGray *gray) const = 0;
    virtual void getRGB(const GfxColor *color, GfxRGB *rgb) const = 0;
    virtual void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const = 0;
    virtual void getDeviceN(const GfxColor *color, GfxColor *deviceN) const = 0;
    virtual void getGrayLine(unsigned char * /*in*/, unsigned char * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getGrayLine this should not happen"); }
    virtual void getRGBLine(unsigned char * /*in*/, unsigned int * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getRGBLine (first variant) this should not happen"); }
    virtual void getRGBLine(unsigned char * /*in*/, unsigned char * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getRGBLine (second variant) this should not happen"); }
    virtual void getRGBXLine(unsigned char * /*in*/, unsigned char * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getRGBXLine this should not happen"); }
    virtual void getCMYKLine(unsigned char * /*in*/, unsigned char * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getCMYKLine this should not happen"); }
    virtual void getDeviceNLine(unsigned char * /*in*/, unsigned char * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getDeviceNLine this should not happen"); }

    // create mapping for spot colorants
    virtual void createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps);
    int *getMapping() const { return mapping; }

    // Does this ColorSpace support getRGBLine?
    virtual bool useGetRGBLine() const { return false; }
    // Does this ColorSpace support getGrayLine?
    virtual bool useGetGrayLine() const { return false; }
    // Does this ColorSpace support getCMYKLine?
    virtual bool useGetCMYKLine() const { return false; }
    // Does this ColorSpace support getDeviceNLine?
    virtual bool useGetDeviceNLine() const { return false; }

    // Return the number of color components.
    virtual int getNComps() const = 0;

    // Get this color space's default color.
    virtual void getDefaultColor(GfxColor *color) const = 0;

    // Return the default ranges for each component, assuming an image
    // with a max pixel value of <maxImgPixel>.
    virtual void getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const;

    // Returns true if painting operations in this color space never
    // mark the page (e.g., the "None" colorant).
    virtual bool isNonMarking() const { return false; }

    // Return the color space's overprint mask.
    unsigned int getOverprintMask() const { return overprintMask; }

    // Return the number of color space modes
    static int getNumColorSpaceModes();

    // Return the name of the <idx>th color space mode.
    static const char *getColorSpaceModeName(int idx);

protected:
    unsigned int overprintMask;
    int *mapping;
};

//------------------------------------------------------------------------
// GfxDeviceGrayColorSpace
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxDeviceGrayColorSpace : public GfxColorSpace
{
public:
    GfxDeviceGrayColorSpace();
    ~GfxDeviceGrayColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csDeviceGray; }

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;
    void getGrayLine(unsigned char *in, unsigned char *out, int length) override;
    void getRGBLine(unsigned char *in, unsigned int *out, int length) override;
    void getRGBLine(unsigned char *in, unsigned char *out, int length) override;
    void getRGBXLine(unsigned char *in, unsigned char *out, int length) override;
    void getCMYKLine(unsigned char *in, unsigned char *out, int length) override;
    void getDeviceNLine(unsigned char *in, unsigned char *out, int length) override;

    bool useGetRGBLine() const override { return true; }
    bool useGetGrayLine() const override { return true; }
    bool useGetCMYKLine() const override { return true; }
    bool useGetDeviceNLine() const override { return true; }

    int getNComps() const override { return 1; }
    void getDefaultColor(GfxColor *color) const override;

private:
};

//------------------------------------------------------------------------
// GfxCalGrayColorSpace
//------------------------------------------------------------------------

class GfxCalGrayColorSpace : public GfxColorSpace
{
public:
    GfxCalGrayColorSpace();
    ~GfxCalGrayColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csCalGray; }

    // Construct a CalGray color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(Array *arr, GfxState *state);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;

    int getNComps() const override { return 1; }
    void getDefaultColor(GfxColor *color) const override;

    // CalGray-specific access.
    double getWhiteX() const { return whiteX; }
    double getWhiteY() const { return whiteY; }
    double getWhiteZ() const { return whiteZ; }
    double getBlackX() const { return blackX; }
    double getBlackY() const { return blackY; }
    double getBlackZ() const { return blackZ; }
    double getGamma() const { return gamma; }

private:
    double whiteX, whiteY, whiteZ; // white point
    double blackX, blackY, blackZ; // black point
    double gamma; // gamma value
    void getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const;
#ifdef USE_CMS
    std::shared_ptr<GfxColorTransform> transform;
#endif
};

//------------------------------------------------------------------------
// GfxDeviceRGBColorSpace
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxDeviceRGBColorSpace : public GfxColorSpace
{
public:
    GfxDeviceRGBColorSpace();
    ~GfxDeviceRGBColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csDeviceRGB; }

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;
    void getGrayLine(unsigned char *in, unsigned char *out, int length) override;
    void getRGBLine(unsigned char *in, unsigned int *out, int length) override;
    void getRGBLine(unsigned char *in, unsigned char *out, int length) override;
    void getRGBXLine(unsigned char *in, unsigned char *out, int length) override;
    void getCMYKLine(unsigned char *in, unsigned char *out, int length) override;
    void getDeviceNLine(unsigned char *in, unsigned char *out, int length) override;

    bool useGetRGBLine() const override { return true; }
    bool useGetGrayLine() const override { return true; }
    bool useGetCMYKLine() const override { return true; }
    bool useGetDeviceNLine() const override { return true; }

    int getNComps() const override { return 3; }
    void getDefaultColor(GfxColor *color) const override;

private:
};

//------------------------------------------------------------------------
// GfxCalRGBColorSpace
//------------------------------------------------------------------------

class GfxCalRGBColorSpace : public GfxColorSpace
{
public:
    GfxCalRGBColorSpace();
    ~GfxCalRGBColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csCalRGB; }

    // Construct a CalRGB color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(Array *arr, GfxState *state);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;

    int getNComps() const override { return 3; }
    void getDefaultColor(GfxColor *color) const override;

    // CalRGB-specific access.
    double getWhiteX() const { return whiteX; }
    double getWhiteY() const { return whiteY; }
    double getWhiteZ() const { return whiteZ; }
    double getBlackX() const { return blackX; }
    double getBlackY() const { return blackY; }
    double getBlackZ() const { return blackZ; }
    double getGammaR() const { return gammaR; }
    double getGammaG() const { return gammaG; }
    double getGammaB() const { return gammaB; }
    const double *getMatrix() const { return mat; }

private:
    double whiteX, whiteY, whiteZ; // white point
    double blackX, blackY, blackZ; // black point
    double gammaR, gammaG, gammaB; // gamma values
    double mat[9]; // ABC -> XYZ transform matrix
    void getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const;
#ifdef USE_CMS
    std::shared_ptr<GfxColorTransform> transform;
#endif
};

//------------------------------------------------------------------------
// GfxDeviceCMYKColorSpace
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxDeviceCMYKColorSpace : public GfxColorSpace
{
public:
    GfxDeviceCMYKColorSpace();
    ~GfxDeviceCMYKColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csDeviceCMYK; }

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;
    void getRGBLine(unsigned char *in, unsigned int *out, int length) override;
    void getRGBLine(unsigned char *, unsigned char *out, int length) override;
    void getRGBXLine(unsigned char *in, unsigned char *out, int length) override;
    void getCMYKLine(unsigned char *in, unsigned char *out, int length) override;
    void getDeviceNLine(unsigned char *in, unsigned char *out, int length) override;
    bool useGetRGBLine() const override { return true; }
    bool useGetCMYKLine() const override { return true; }
    bool useGetDeviceNLine() const override { return true; }

    int getNComps() const override { return 4; }
    void getDefaultColor(GfxColor *color) const override;

private:
};

//------------------------------------------------------------------------
// GfxLabColorSpace
//------------------------------------------------------------------------

class GfxLabColorSpace : public GfxColorSpace
{
public:
    GfxLabColorSpace();
    ~GfxLabColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csLab; }

    // Construct a Lab color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(Array *arr, GfxState *state);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;

    int getNComps() const override { return 3; }
    void getDefaultColor(GfxColor *color) const override;

    void getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const override;

    // Lab-specific access.
    double getWhiteX() const { return whiteX; }
    double getWhiteY() const { return whiteY; }
    double getWhiteZ() const { return whiteZ; }
    double getBlackX() const { return blackX; }
    double getBlackY() const { return blackY; }
    double getBlackZ() const { return blackZ; }
    double getAMin() const { return aMin; }
    double getAMax() const { return aMax; }
    double getBMin() const { return bMin; }
    double getBMax() const { return bMax; }

private:
    double whiteX, whiteY, whiteZ; // white point
    double blackX, blackY, blackZ; // black point
    double aMin, aMax, bMin, bMax; // range for the a and b components
    void getXYZ(const GfxColor *color, double *pX, double *pY, double *pZ) const;
#ifdef USE_CMS
    std::shared_ptr<GfxColorTransform> transform;
#endif
};

//------------------------------------------------------------------------
// GfxICCBasedColorSpace
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxICCBasedColorSpace : public GfxColorSpace
{
public:
    GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA, const Ref *iccProfileStreamA);
    ~GfxICCBasedColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csICCBased; }

    // Construct an ICCBased color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(Array *arr, OutputDev *out, GfxState *state, int recursion);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;
    void getRGBLine(unsigned char *in, unsigned int *out, int length) override;
    void getRGBLine(unsigned char *in, unsigned char *out, int length) override;
    void getRGBXLine(unsigned char *in, unsigned char *out, int length) override;
    void getCMYKLine(unsigned char *in, unsigned char *out, int length) override;
    void getDeviceNLine(unsigned char *in, unsigned char *out, int length) override;

    bool useGetRGBLine() const override;
    bool useGetCMYKLine() const override;
    bool useGetDeviceNLine() const override;

    int getNComps() const override { return nComps; }
    void getDefaultColor(GfxColor *color) const override;

    void getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const override;

    // ICCBased-specific access.
    GfxColorSpace *getAlt() { return alt; }
    Ref getRef() { return iccProfileStream; }
#ifdef USE_CMS
    char *getPostScriptCSA();
    void buildTransforms(GfxState *state);
    void setProfile(GfxLCMSProfilePtr &profileA) { profile = profileA; }
    GfxLCMSProfilePtr getProfile() { return profile; }
#endif

private:
    int nComps; // number of color components (1, 3, or 4)
    GfxColorSpace *alt; // alternate color space
    double rangeMin[4]; // min values for each component
    double rangeMax[4]; // max values for each component
    Ref iccProfileStream; // the ICC profile
#ifdef USE_CMS
    GfxLCMSProfilePtr profile;
    char *psCSA;
    int getIntent() { return (transform != nullptr) ? transform->getIntent() : 0; }
    std::shared_ptr<GfxColorTransform> transform;
    std::shared_ptr<GfxColorTransform> lineTransform; // color transform for line
    mutable std::map<unsigned int, unsigned int> cmsCache;
#endif
};
//------------------------------------------------------------------------
// GfxIndexedColorSpace
//------------------------------------------------------------------------

class GfxIndexedColorSpace : public GfxColorSpace
{
public:
    GfxIndexedColorSpace(GfxColorSpace *baseA, int indexHighA);
    ~GfxIndexedColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csIndexed; }

    // Construct an Indexed color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;
    void getRGBLine(unsigned char *in, unsigned int *out, int length) override;
    void getRGBLine(unsigned char *in, unsigned char *out, int length) override;
    void getRGBXLine(unsigned char *in, unsigned char *out, int length) override;
    void getCMYKLine(unsigned char *in, unsigned char *out, int length) override;
    void getDeviceNLine(unsigned char *in, unsigned char *out, int length) override;

    bool useGetRGBLine() const override { return true; }
    bool useGetCMYKLine() const override { return true; }
    bool useGetDeviceNLine() const override { return true; }

    int getNComps() const override { return 1; }
    void getDefaultColor(GfxColor *color) const override;

    void getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) const override;

    // Indexed-specific access.
    GfxColorSpace *getBase() { return base; }
    int getIndexHigh() const { return indexHigh; }
    unsigned char *getLookup() { return lookup; }
    GfxColor *mapColorToBase(const GfxColor *color, GfxColor *baseColor) const;
    unsigned int getOverprintMask() const { return base->getOverprintMask(); }
    void createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps) override { base->createMapping(separationList, maxSepComps); }

private:
    GfxColorSpace *base; // base color space
    int indexHigh; // max pixel value
    unsigned char *lookup; // lookup table
};

//------------------------------------------------------------------------
// GfxSeparationColorSpace
//------------------------------------------------------------------------

class GfxSeparationColorSpace : public GfxColorSpace
{
public:
    GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA);
    ~GfxSeparationColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csSeparation; }

    // Construct a Separation color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;

    void createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps) override;

    int getNComps() const override { return 1; }
    void getDefaultColor(GfxColor *color) const override;

    bool isNonMarking() const override { return nonMarking; }

    // Separation-specific access.
    const GooString *getName() const { return name; }
    GfxColorSpace *getAlt() { return alt; }
    const Function *getFunc() const { return func; }

private:
    GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA, Function *funcA, bool nonMarkingA, unsigned int overprintMaskA, int *mappingA);

    GooString *name; // colorant name
    GfxColorSpace *alt; // alternate color space
    Function *func; // tint transform (into alternate color space)
    bool nonMarking;
};

//------------------------------------------------------------------------
// GfxDeviceNColorSpace
//------------------------------------------------------------------------

class GfxDeviceNColorSpace : public GfxColorSpace
{
public:
    GfxDeviceNColorSpace(int nCompsA, std::vector<std::string> &&namesA, GfxColorSpace *alt, Function *func, std::vector<GfxSeparationColorSpace *> *sepsCS);
    ~GfxDeviceNColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csDeviceN; }

    // Construct a DeviceN color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;

    void createMapping(std::vector<GfxSeparationColorSpace *> *separationList, int maxSepComps) override;

    int getNComps() const override { return nComps; }
    void getDefaultColor(GfxColor *color) const override;

    bool isNonMarking() const override { return nonMarking; }

    // DeviceN-specific access.
    const std::string &getColorantName(int i) const { return names[i]; }
    GfxColorSpace *getAlt() { return alt; }
    const Function *getTintTransformFunc() const { return func; }

private:
    GfxDeviceNColorSpace(int nCompsA, const std::vector<std::string> &namesA, GfxColorSpace *alt, Function *func, std::vector<GfxSeparationColorSpace *> *sepsCSA, int *mappingA, bool nonMarkingA, unsigned int overprintMaskA);

    const int nComps; // number of components
    const std::vector<std::string> names; // colorant names
    GfxColorSpace *alt; // alternate color space
    Function *func; // tint transform (into alternate color space)
    bool nonMarking;
    std::vector<GfxSeparationColorSpace *> *sepsCS; // list of separation cs for spot colorants;
};

//------------------------------------------------------------------------
// GfxPatternColorSpace
//------------------------------------------------------------------------

class GfxPatternColorSpace : public GfxColorSpace
{
public:
    explicit GfxPatternColorSpace(GfxColorSpace *underA);
    ~GfxPatternColorSpace() override;
    GfxColorSpace *copy() const override;
    GfxColorSpaceMode getMode() const override { return csPattern; }

    // Construct a Pattern color space.  Returns nullptr if unsuccessful.
    static GfxColorSpace *parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion);

    void getGray(const GfxColor *color, GfxGray *gray) const override;
    void getRGB(const GfxColor *color, GfxRGB *rgb) const override;
    void getCMYK(const GfxColor *color, GfxCMYK *cmyk) const override;
    void getDeviceN(const GfxColor *color, GfxColor *deviceN) const override;

    int getNComps() const override { return 0; }
    void getDefaultColor(GfxColor *color) const override;

    // Pattern-specific access.
    GfxColorSpace *getUnder() { return under; }

private:
    GfxColorSpace *under; // underlying color space (for uncolored
                          //   patterns)
};

//------------------------------------------------------------------------
// GfxPattern
//------------------------------------------------------------------------

class GfxPattern
{
public:
    GfxPattern(int typeA, int patternRefNumA);
    virtual ~GfxPattern();

    GfxPattern(const GfxPattern &) = delete;
    GfxPattern &operator=(const GfxPattern &other) = delete;

    static GfxPattern *parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state, int patternRefNum);

    virtual GfxPattern *copy() const = 0;

    int getType() const { return type; }

    int getPatternRefNum() const { return patternRefNum; }

private:
    int type;
    int patternRefNum;
};

//------------------------------------------------------------------------
// GfxTilingPattern
//------------------------------------------------------------------------

class GfxTilingPattern : public GfxPattern
{
public:
    static GfxTilingPattern *parse(Object *patObj, int patternRefNum);
    ~GfxTilingPattern() override;

    GfxPattern *copy() const override;

    int getPaintType() const { return paintType; }
    int getTilingType() const { return tilingType; }
    const double *getBBox() const { return bbox; }
    double getXStep() const { return xStep; }
    double getYStep() const { return yStep; }
    Dict *getResDict() { return resDict.isDict() ? resDict.getDict() : (Dict *)nullptr; }
    const double *getMatrix() const { return matrix; }
    Object *getContentStream() { return &contentStream; }

private:
    GfxTilingPattern(int paintTypeA, int tilingTypeA, const double *bboxA, double xStepA, double yStepA, const Object *resDictA, const double *matrixA, const Object *contentStreamA, int patternRefNumA);

    int paintType;
    int tilingType;
    double bbox[4];
    double xStep, yStep;
    Object resDict;
    double matrix[6];
    Object contentStream;
};

//------------------------------------------------------------------------
// GfxShadingPattern
//------------------------------------------------------------------------

class GfxShadingPattern : public GfxPattern
{
public:
    static GfxShadingPattern *parse(GfxResources *res, Object *patObj, OutputDev *out, GfxState *state, int patternRefNum);
    ~GfxShadingPattern() override;

    GfxPattern *copy() const override;

    GfxShading *getShading() { return shading; }
    const double *getMatrix() const { return matrix; }

private:
    GfxShadingPattern(GfxShading *shadingA, const double *matrixA, int patternRefNumA);

    GfxShading *shading;
    double matrix[6];
};

//------------------------------------------------------------------------
// GfxShading
//------------------------------------------------------------------------

class GfxShading
{
public:
    explicit GfxShading(int typeA);
    explicit GfxShading(const GfxShading *shading);
    virtual ~GfxShading();

    GfxShading(const GfxShading &) = delete;
    GfxShading &operator=(const GfxShading &other) = delete;

    static GfxShading *parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state);

    virtual GfxShading *copy() const = 0;

    int getType() const { return type; }
    GfxColorSpace *getColorSpace() { return colorSpace; }
    const GfxColor *getBackground() const { return &background; }
    bool getHasBackground() const { return hasBackground; }
    void getBBox(double *xMinA, double *yMinA, double *xMaxA, double *yMaxA) const
    {
        *xMinA = bbox_xMin;
        *yMinA = bbox_yMin;
        *xMaxA = bbox_xMax;
        *yMaxA = bbox_yMax;
    }
    bool getHasBBox() const { return hasBBox; }

protected:
    virtual bool init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state);

    // 1: Function-based shading
    // 2: Axial shading
    // 3: Radial shading
    // 4: Free-form Gouraud-shaded triangle mesh
    // 5: Lattice-form Gouraud-shaded triangle mesh
    // 6: Coons patch mesh
    // 7: Tensor-product patch mesh
    int type;
    bool hasBackground;
    bool hasBBox;
    GfxColorSpace *colorSpace;
    GfxColor background;
    double bbox_xMin, bbox_yMin, bbox_xMax, bbox_yMax;
};

//------------------------------------------------------------------------
// GfxUnivariateShading
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxUnivariateShading : public GfxShading
{
public:
    GfxUnivariateShading(int typeA, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A);
    explicit GfxUnivariateShading(const GfxUnivariateShading *shading);
    ~GfxUnivariateShading() override;

    double getDomain0() const { return t0; }
    double getDomain1() const { return t1; }
    bool getExtend0() const { return extend0; }
    bool getExtend1() const { return extend1; }
    int getNFuncs() const { return funcs.size(); }
    const Function *getFunc(int i) const { return funcs[i].get(); }
    // returns the nComps of the shading
    // i.e. how many positions of color have been set
    int getColor(double t, GfxColor *color);

    void setupCache(const Matrix *ctm, double xMin, double yMin, double xMax, double yMax);

    virtual void getParameterRange(double *lower, double *upper, double xMin, double yMin, double xMax, double yMax) = 0;

    virtual double getDistance(double sMin, double sMax) const = 0;

protected:
    bool init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) override;

private:
    double t0, t1;
    std::vector<std::unique_ptr<Function>> funcs;
    bool extend0, extend1;

    int cacheSize, lastMatch;
    double *cacheBounds;
    double *cacheCoeff;
    double *cacheValues;
};

//------------------------------------------------------------------------
// GfxFunctionShading
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxFunctionShading : public GfxShading
{
public:
    GfxFunctionShading(double x0A, double y0A, double x1A, double y1A, const double *matrixA, std::vector<std::unique_ptr<Function>> &&funcsA);
    explicit GfxFunctionShading(const GfxFunctionShading *shading);
    ~GfxFunctionShading() override;

    static GfxFunctionShading *parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state);

    GfxShading *copy() const override;

    void getDomain(double *x0A, double *y0A, double *x1A, double *y1A) const
    {
        *x0A = x0;
        *y0A = y0;
        *x1A = x1;
        *y1A = y1;
    }
    const double *getMatrix() const { return matrix; }
    int getNFuncs() const { return funcs.size(); }
    const Function *getFunc(int i) const { return funcs[i].get(); }
    void getColor(double x, double y, GfxColor *color) const;

protected:
    bool init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) override;

private:
    double x0, y0, x1, y1;
    double matrix[6];
    std::vector<std::unique_ptr<Function>> funcs;
};

//------------------------------------------------------------------------
// GfxAxialShading
//------------------------------------------------------------------------

class GfxAxialShading : public GfxUnivariateShading
{
public:
    GfxAxialShading(double x0A, double y0A, double x1A, double y1A, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A);
    explicit GfxAxialShading(const GfxAxialShading *shading);
    ~GfxAxialShading() override;

    static GfxAxialShading *parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state);

    GfxShading *copy() const override;

    void getCoords(double *x0A, double *y0A, double *x1A, double *y1A) const
    {
        *x0A = x0;
        *y0A = y0;
        *x1A = x1;
        *y1A = y1;
    }

    void getParameterRange(double *lower, double *upper, double xMin, double yMin, double xMax, double yMax) override;

    double getDistance(double sMin, double sMax) const override;

private:
    double x0, y0, x1, y1;
};

//------------------------------------------------------------------------
// GfxRadialShading
//------------------------------------------------------------------------

class GfxRadialShading : public GfxUnivariateShading
{
public:
    GfxRadialShading(double x0A, double y0A, double r0A, double x1A, double y1A, double r1A, double t0A, double t1A, std::vector<std::unique_ptr<Function>> &&funcsA, bool extend0A, bool extend1A);
    explicit GfxRadialShading(const GfxRadialShading *shading);
    ~GfxRadialShading() override;

    static GfxRadialShading *parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state);

    GfxShading *copy() const override;

    void getCoords(double *x0A, double *y0A, double *r0A, double *x1A, double *y1A, double *r1A) const
    {
        *x0A = x0;
        *y0A = y0;
        *r0A = r0;
        *x1A = x1;
        *y1A = y1;
        *r1A = r1;
    }

    void getParameterRange(double *lower, double *upper, double xMin, double yMin, double xMax, double yMax) override;

    double getDistance(double sMin, double sMax) const override;

private:
    double x0, y0, r0, x1, y1, r1;
};

//------------------------------------------------------------------------
// GfxGouraudTriangleShading
//------------------------------------------------------------------------

struct GfxGouraudVertex
{
    double x, y;
    GfxColor color;
};

class POPPLER_PRIVATE_EXPORT GfxGouraudTriangleShading : public GfxShading
{
public:
    GfxGouraudTriangleShading(int typeA, GfxGouraudVertex *verticesA, int nVerticesA, int (*trianglesA)[3], int nTrianglesA, std::vector<std::unique_ptr<Function>> &&funcsA);
    explicit GfxGouraudTriangleShading(const GfxGouraudTriangleShading *shading);
    ~GfxGouraudTriangleShading() override;

    static GfxGouraudTriangleShading *parse(GfxResources *res, int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *state);

    GfxShading *copy() const override;

    int getNTriangles() const { return nTriangles; }

    bool isParameterized() const { return !funcs.empty(); }

    /**
     * @precondition isParameterized() == true
     */
    double getParameterDomainMin() const
    {
        assert(isParameterized());
        return funcs[0]->getDomainMin(0);
    }

    /**
     * @precondition isParameterized() == true
     */
    double getParameterDomainMax() const
    {
        assert(isParameterized());
        return funcs[0]->getDomainMax(0);
    }

    /**
     * @precondition isParameterized() == false
     */
    void getTriangle(int i, double *x0, double *y0, GfxColor *color0, double *x1, double *y1, GfxColor *color1, double *x2, double *y2, GfxColor *color2);

    /**
     * Variant for functions.
     *
     * @precondition isParameterized() == true
     */
    void getTriangle(int i, double *x0, double *y0, double *color0, double *x1, double *y1, double *color1, double *x2, double *y2, double *color2);

    void getParameterizedColor(double t, GfxColor *color) const;

protected:
    bool init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) override;

private:
    GfxGouraudVertex *vertices;
    int nVertices;
    int (*triangles)[3];
    int nTriangles;
    std::vector<std::unique_ptr<Function>> funcs;
};

//------------------------------------------------------------------------
// GfxPatchMeshShading
//------------------------------------------------------------------------

/**
 * A tensor product cubic bezier patch consisting of 4x4 points and 4 color
 * values.
 *
 * See the Shading Type 7 specifications. Note that Shading Type 6 is also
 * represented using GfxPatch.
 */
struct GfxPatch
{
    /**
     * Represents a single color value for the patch.
     */
    struct ColorValue
    {
        /**
         * For parameterized patches, only element 0 is valid; it contains
         * the single parameter.
         *
         * For non-parameterized patches, c contains all color components
         * as decoded from the input stream. In this case, you will need to
         * use dblToCol() before assigning them to GfxColor.
         */
        double c[gfxColorMaxComps];
    };

    double x[4][4];
    double y[4][4];
    ColorValue color[2][2];
};

class POPPLER_PRIVATE_EXPORT GfxPatchMeshShading : public GfxShading
{
public:
    GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA, std::vector<std::unique_ptr<Function>> &&funcsA);
    explicit GfxPatchMeshShading(const GfxPatchMeshShading *shading);
    ~GfxPatchMeshShading() override;

    static GfxPatchMeshShading *parse(GfxResources *res, int typeA, Dict *dict, Stream *str, OutputDev *out, GfxState *state);

    GfxShading *copy() const override;

    int getNPatches() const { return nPatches; }
    const GfxPatch *getPatch(int i) const { return &patches[i]; }

    bool isParameterized() const { return !funcs.empty(); }

    /**
     * @precondition isParameterized() == true
     */
    double getParameterDomainMin() const
    {
        assert(isParameterized());
        return funcs[0]->getDomainMin(0);
    }

    /**
     * @precondition isParameterized() == true
     */
    double getParameterDomainMax() const
    {
        assert(isParameterized());
        return funcs[0]->getDomainMax(0);
    }

    void getParameterizedColor(double t, GfxColor *color) const;

protected:
    bool init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) override;

private:
    GfxPatch *patches;
    int nPatches;
    std::vector<std::unique_ptr<Function>> funcs;
};

//------------------------------------------------------------------------
// GfxImageColorMap
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxImageColorMap
{
public:
    // Constructor.
    GfxImageColorMap(int bitsA, Object *decode, GfxColorSpace *colorSpaceA);

    // Destructor.
    ~GfxImageColorMap();

    GfxImageColorMap(const GfxImageColorMap &) = delete;
    GfxImageColorMap &operator=(const GfxImageColorMap &) = delete;

    // Return a copy of this color map.
    GfxImageColorMap *copy() const { return new GfxImageColorMap(this); }

    // Is color map valid?
    bool isOk() const { return ok; }

    // Get the color space.
    GfxColorSpace *getColorSpace() { return colorSpace; }

    // Get stream decoding info.
    int getNumPixelComps() const { return nComps; }
    int getBits() const { return bits; }

    // Get decode table.
    double getDecodeLow(int i) const { return decodeLow[i]; }
    double getDecodeHigh(int i) const { return decodeLow[i] + decodeRange[i]; }

    bool useRGBLine() const { return (colorSpace2 && colorSpace2->useGetRGBLine()) || (!colorSpace2 && colorSpace->useGetRGBLine()); }
    bool useCMYKLine() const { return (colorSpace2 && colorSpace2->useGetCMYKLine()) || (!colorSpace2 && colorSpace->useGetCMYKLine()); }
    bool useDeviceNLine() const { return (colorSpace2 && colorSpace2->useGetDeviceNLine()) || (!colorSpace2 && colorSpace->useGetDeviceNLine()); }

    // Convert an image pixel to a color.
    void getGray(const unsigned char *x, GfxGray *gray);
    void getRGB(const unsigned char *x, GfxRGB *rgb);
    void getRGBLine(unsigned char *in, unsigned int *out, int length);
    void getRGBLine(unsigned char *in, unsigned char *out, int length);
    void getRGBXLine(unsigned char *in, unsigned char *out, int length);
    void getGrayLine(unsigned char *in, unsigned char *out, int length);
    void getCMYKLine(unsigned char *in, unsigned char *out, int length);
    void getDeviceNLine(unsigned char *in, unsigned char *out, int length);
    void getCMYK(const unsigned char *x, GfxCMYK *cmyk);
    void getDeviceN(const unsigned char *x, GfxColor *deviceN);
    void getColor(const unsigned char *x, GfxColor *color);

    // Matte color ops
    void setMatteColor(const GfxColor *color)
    {
        useMatte = true;
        matteColor = *color;
    }
    const GfxColor *getMatteColor() const { return (useMatte) ? &matteColor : nullptr; }

private:
    explicit GfxImageColorMap(const GfxImageColorMap *colorMap);

    GfxColorSpace *colorSpace; // the image color space
    int bits; // bits per component
    int nComps; // number of components in a pixel
    GfxColorSpace *colorSpace2; // secondary color space
    int nComps2; // number of components in colorSpace2
    GfxColorComp * // lookup table
            lookup[gfxColorMaxComps];
    GfxColorComp * // optimized case lookup table
            lookup2[gfxColorMaxComps];
    unsigned char *byte_lookup;
    double // minimum values for each component
            decodeLow[gfxColorMaxComps];
    double // max - min value for each component
            decodeRange[gfxColorMaxComps];
    bool useMatte;
    GfxColor matteColor;
    bool ok;
};

//------------------------------------------------------------------------
// GfxSubpath and GfxPath
//------------------------------------------------------------------------

class GfxSubpath
{
public:
    // Constructor.
    GfxSubpath(double x1, double y1);

    // Destructor.
    ~GfxSubpath();

    GfxSubpath(const GfxSubpath &) = delete;
    GfxSubpath &operator=(const GfxSubpath &) = delete;

    // Copy.
    GfxSubpath *copy() const { return new GfxSubpath(this); }

    // Get points.
    int getNumPoints() const { return n; }
    double getX(int i) const { return x[i]; }
    double getY(int i) const { return y[i]; }
    bool getCurve(int i) const { return curve[i]; }

    void setX(int i, double a) { x[i] = a; }
    void setY(int i, double a) { y[i] = a; }

    // Get last point.
    double getLastX() const { return x[n - 1]; }
    double getLastY() const { return y[n - 1]; }

    // Add a line segment.
    void lineTo(double x1, double y1);

    // Add a Bezier curve.
    void curveTo(double x1, double y1, double x2, double y2, double x3, double y3);

    // Close the subpath.
    void close();
    bool isClosed() const { return closed; }

    // Add (<dx>, <dy>) to each point in the subpath.
    void offset(double dx, double dy);

private:
    double *x, *y; // points
    bool *curve; // curve[i] => point i is a control point
                 //   for a Bezier curve
    int n; // number of points
    int size; // size of x/y arrays
    bool closed; // set if path is closed

    explicit GfxSubpath(const GfxSubpath *subpath);
};

class POPPLER_PRIVATE_EXPORT GfxPath
{
public:
    // Constructor.
    GfxPath();

    // Destructor.
    ~GfxPath();

    GfxPath(const GfxPath &) = delete;
    GfxPath &operator=(const GfxPath &) = delete;

    // Copy.
    GfxPath *copy() const { return new GfxPath(justMoved, firstX, firstY, subpaths, n, size); }

    // Is there a current point?
    bool isCurPt() const { return n > 0 || justMoved; }

    // Is the path non-empty, i.e., is there at least one segment?
    bool isPath() const { return n > 0; }

    // Get subpaths.
    int getNumSubpaths() const { return n; }
    GfxSubpath *getSubpath(int i) { return subpaths[i]; }
    const GfxSubpath *getSubpath(int i) const { return subpaths[i]; }

    // Get last point on last subpath.
    double getLastX() const { return subpaths[n - 1]->getLastX(); }
    double getLastY() const { return subpaths[n - 1]->getLastY(); }

    // Move the current point.
    void moveTo(double x, double y);

    // Add a segment to the last subpath.
    void lineTo(double x, double y);

    // Add a Bezier curve to the last subpath
    void curveTo(double x1, double y1, double x2, double y2, double x3, double y3);

    // Close the last subpath.
    void close();

    // Append <path> to <this>.
    void append(GfxPath *path);

    // Add (<dx>, <dy>) to each point in the path.
    void offset(double dx, double dy);

private:
    bool justMoved; // set if a new subpath was just started
    double firstX, firstY; // first point in new subpath
    GfxSubpath **subpaths; // subpaths
    int n; // number of subpaths
    int size; // size of subpaths array

    GfxPath(bool justMoved1, double firstX1, double firstY1, GfxSubpath **subpaths1, int n1, int size1);
};

//------------------------------------------------------------------------
// GfxState
//------------------------------------------------------------------------

class POPPLER_PRIVATE_EXPORT GfxState
{
public:
    /**
     * When GfxState::getReusablePath() is invoked, the currently active
     * path is taken per reference and its coordinates can be re-edited.
     *
     * A ReusablePathIterator is intended to reduce overhead when the same
     * path type is used a lot of times, only with different coordinates. It
     * allows just to update the coordinates (occurring in the same order as
     * in the original path).
     */
    class ReusablePathIterator
    {
    public:
        /**
         * Creates the ReusablePathIterator. This should only be done from
         * GfxState::getReusablePath().
         *
         * @param path the path as it is used so far. Changing this path,
         * deleting it or starting a new path from scratch will most likely
         * invalidate the iterator (and may cause serious problems). Make
         * sure the path's memory structure is not changed during the
         * lifetime of the ReusablePathIterator.
         */
        explicit ReusablePathIterator(GfxPath *path);

        /**
         * Returns true if and only if the current iterator position is
         * beyond the last valid point.
         *
         * A call to setCoord() will be undefined.
         */
        bool isEnd() const;

        /**
         * Advances the iterator.
         */
        void next();

        /**
         * Updates the coordinates associated to the current iterator
         * position.
         */
        void setCoord(double x, double y);

        /**
         * Resets the iterator.
         */
        void reset();

    private:
        GfxPath *path;
        int subPathOff;

        int coordOff;
        int numCoords;

        GfxSubpath *curSubPath;
    };

    // Construct a default GfxState, for a device with resolution <hDPI>
    // x <vDPI>, page box <pageBox>, page rotation <rotateA>, and
    // coordinate system specified by <upsideDown>.
    GfxState(double hDPIA, double vDPIA, const PDFRectangle *pageBox, int rotateA, bool upsideDown);

    // Destructor.
    ~GfxState();

    GfxState(const GfxState &) = delete;
    GfxState &operator=(const GfxState &) = delete;

    // Copy.
    GfxState *copy(bool copyPath = false) const { return new GfxState(this, copyPath); }

    // Accessors.
    double getHDPI() const { return hDPI; }
    double getVDPI() const { return vDPI; }
    const double *getCTM() const { return ctm; }
    void getCTM(Matrix *m) const { memcpy(m->m, ctm, sizeof m->m); }
    double getX1() const { return px1; }
    double getY1() const { return py1; }
    double getX2() const { return px2; }
    double getY2() const { return py2; }
    double getPageWidth() const { return pageWidth; }
    double getPageHeight() const { return pageHeight; }
    int getRotate() const { return rotate; }
    const GfxColor *getFillColor() const { return &fillColor; }
    const GfxColor *getStrokeColor() const { return &strokeColor; }
    void getFillGray(GfxGray *gray) { fillColorSpace->getGray(&fillColor, gray); }
    void getStrokeGray(GfxGray *gray) { strokeColorSpace->getGray(&strokeColor, gray); }
    void getFillRGB(GfxRGB *rgb) const { fillColorSpace->getRGB(&fillColor, rgb); }
    void getStrokeRGB(GfxRGB *rgb) const { strokeColorSpace->getRGB(&strokeColor, rgb); }
    void getFillCMYK(GfxCMYK *cmyk) { fillColorSpace->getCMYK(&fillColor, cmyk); }
    void getFillDeviceN(GfxColor *deviceN) { fillColorSpace->getDeviceN(&fillColor, deviceN); }
    void getStrokeCMYK(GfxCMYK *cmyk) { strokeColorSpace->getCMYK(&strokeColor, cmyk); }
    void getStrokeDeviceN(GfxColor *deviceN) { strokeColorSpace->getDeviceN(&strokeColor, deviceN); }
    GfxColorSpace *getFillColorSpace() { return fillColorSpace; }
    GfxColorSpace *getStrokeColorSpace() { return strokeColorSpace; }
    GfxPattern *getFillPattern() { return fillPattern; }
    GfxPattern *getStrokePattern() { return strokePattern; }
    GfxBlendMode getBlendMode() const { return blendMode; }
    double getFillOpacity() const { return fillOpacity; }
    double getStrokeOpacity() const { return strokeOpacity; }
    bool getFillOverprint() const { return fillOverprint; }
    bool getStrokeOverprint() const { return strokeOverprint; }
    int getOverprintMode() const { return overprintMode; }
    Function **getTransfer() { return transfer; }
    double getLineWidth() const { return lineWidth; }
    void getLineDash(double **dash, int *length, double *start)
    {
        *dash = lineDash;
        *length = lineDashLength;
        *start = lineDashStart;
    }
    int getFlatness() const { return flatness; }
    int getLineJoin() const { return lineJoin; }
    int getLineCap() const { return lineCap; }
    double getMiterLimit() const { return miterLimit; }
    bool getStrokeAdjust() const { return strokeAdjust; }
    bool getAlphaIsShape() const { return alphaIsShape; }
    bool getTextKnockout() const { return textKnockout; }
    GfxFont *getFont() const { return font; }
    double getFontSize() const { return fontSize; }
    const double *getTextMat() const { return textMat; }
    double getCharSpace() const { return charSpace; }
    double getWordSpace() const { return wordSpace; }
    double getHorizScaling() const { return horizScaling; }
    double getLeading() const { return leading; }
    double getRise() const { return rise; }
    int getRender() const { return render; }
    const char *getRenderingIntent() const { return renderingIntent; }
    const GfxPath *getPath() const { return path; }
    void setPath(GfxPath *pathA);
    double getCurX() const { return curX; }
    double getCurY() const { return curY; }
    void getClipBBox(double *xMin, double *yMin, double *xMax, double *yMax) const
    {
        *xMin = clipXMin;
        *yMin = clipYMin;
        *xMax = clipXMax;
        *yMax = clipYMax;
    }
    void getUserClipBBox(double *xMin, double *yMin, double *xMax, double *yMax) const;
    double getLineX() const { return lineX; }
    double getLineY() const { return lineY; }

    // Is there a current point/path?
    bool isCurPt() const { return path->isCurPt(); }
    bool isPath() const { return path->isPath(); }

    // Transforms.
    void transform(double x1, double y1, double *x2, double *y2) const
    {
        *x2 = ctm[0] * x1 + ctm[2] * y1 + ctm[4];
        *y2 = ctm[1] * x1 + ctm[3] * y1 + ctm[5];
    }
    void transformDelta(double x1, double y1, double *x2, double *y2) const
    {
        *x2 = ctm[0] * x1 + ctm[2] * y1;
        *y2 = ctm[1] * x1 + ctm[3] * y1;
    }
    void textTransform(double x1, double y1, double *x2, double *y2) const
    {
        *x2 = textMat[0] * x1 + textMat[2] * y1 + textMat[4];
        *y2 = textMat[1] * x1 + textMat[3] * y1 + textMat[5];
    }
    void textTransformDelta(double x1, double y1, double *x2, double *y2) const
    {
        *x2 = textMat[0] * x1 + textMat[2] * y1;
        *y2 = textMat[1] * x1 + textMat[3] * y1;
    }
    double transformWidth(double w) const;
    double getTransformedLineWidth() const { return transformWidth(lineWidth); }
    double getTransformedFontSize() const;
    void getFontTransMat(double *m11, double *m12, double *m21, double *m22) const;

    // Change state parameters.
    void setCTM(double a, double b, double c, double d, double e, double f);
    void concatCTM(double a, double b, double c, double d, double e, double f);
    void shiftCTMAndClip(double tx, double ty);
    void setFillColorSpace(GfxColorSpace *colorSpace);
    void setStrokeColorSpace(GfxColorSpace *colorSpace);
    void setFillColor(const GfxColor *color) { fillColor = *color; }
    void setStrokeColor(const GfxColor *color) { strokeColor = *color; }
    void setFillPattern(GfxPattern *pattern);
    void setStrokePattern(GfxPattern *pattern);
    void setBlendMode(GfxBlendMode mode) { blendMode = mode; }
    void setFillOpacity(double opac) { fillOpacity = opac; }
    void setStrokeOpacity(double opac) { strokeOpacity = opac; }
    void setFillOverprint(bool op) { fillOverprint = op; }
    void setStrokeOverprint(bool op) { strokeOverprint = op; }
    void setOverprintMode(int op) { overprintMode = op; }
    void setTransfer(Function **funcs);
    void setLineWidth(double width) { lineWidth = width; }
    void setLineDash(double *dash, int length, double start);
    void setFlatness(int flatness1) { flatness = flatness1; }
    void setLineJoin(int lineJoin1) { lineJoin = lineJoin1; }
    void setLineCap(int lineCap1) { lineCap = lineCap1; }
    void setMiterLimit(double limit) { miterLimit = limit; }
    void setStrokeAdjust(bool sa) { strokeAdjust = sa; }
    void setAlphaIsShape(bool ais) { alphaIsShape = ais; }
    void setTextKnockout(bool tk) { textKnockout = tk; }
    void setFont(GfxFont *fontA, double fontSizeA);
    void setTextMat(double a, double b, double c, double d, double e, double f)
    {
        textMat[0] = a;
        textMat[1] = b;
        textMat[2] = c;
        textMat[3] = d;
        textMat[4] = e;
        textMat[5] = f;
    }
    void setCharSpace(double space) { charSpace = space; }
    void setWordSpace(double space) { wordSpace = space; }
    void setHorizScaling(double scale) { horizScaling = 0.01 * scale; }
    void setLeading(double leadingA) { leading = leadingA; }
    void setRise(double riseA) { rise = riseA; }
    void setRender(int renderA) { render = renderA; }
    void setRenderingIntent(const char *intent) { strncpy(renderingIntent, intent, 31); }

#ifdef USE_CMS
    void setDisplayProfile(const GfxLCMSProfilePtr &localDisplayProfileA);
    GfxLCMSProfilePtr getDisplayProfile() { return localDisplayProfile; }
    std::shared_ptr<GfxColorTransform> getXYZ2DisplayTransform();
    int getCmsRenderingIntent();
    static GfxLCMSProfilePtr sRGBProfile;
#endif

    void setDefaultGrayColorSpace(GfxColorSpace *cs) { defaultGrayColorSpace = cs; }

    void setDefaultRGBColorSpace(GfxColorSpace *cs) { defaultRGBColorSpace = cs; }

    void setDefaultCMYKColorSpace(GfxColorSpace *cs) { defaultCMYKColorSpace = cs; }

    GfxColorSpace *copyDefaultGrayColorSpace()
    {
        if (defaultGrayColorSpace) {
            return defaultGrayColorSpace->copy();
        }
        return new GfxDeviceGrayColorSpace();
    }

    GfxColorSpace *copyDefaultRGBColorSpace()
    {
        if (defaultRGBColorSpace) {
            return defaultRGBColorSpace->copy();
        }
        return new GfxDeviceRGBColorSpace();
    }

    GfxColorSpace *copyDefaultCMYKColorSpace()
    {
        if (defaultCMYKColorSpace) {
            return defaultCMYKColorSpace->copy();
        }
        return new GfxDeviceCMYKColorSpace();
    }

    // Add to path.
    void moveTo(double x, double y) { path->moveTo(curX = x, curY = y); }
    void lineTo(double x, double y) { path->lineTo(curX = x, curY = y); }
    void curveTo(double x1, double y1, double x2, double y2, double x3, double y3) { path->curveTo(x1, y1, x2, y2, curX = x3, curY = y3); }
    void closePath()
    {
        path->close();
        curX = path->getLastX();
        curY = path->getLastY();
    }
    void clearPath();

    // Update clip region.
    void clip();
    void clipToStrokePath();
    void clipToRect(double xMin, double yMin, double xMax, double yMax);

    // Text position.
    void textSetPos(double tx, double ty)
    {
        lineX = tx;
        lineY = ty;
    }
    void textMoveTo(double tx, double ty)
    {
        lineX = tx;
        lineY = ty;
        textTransform(tx, ty, &curX, &curY);
    }
    void textShift(double tx, double ty);
    void shift(double dx, double dy);

    // Push/pop GfxState on/off stack.
    GfxState *save();
    GfxState *restore();
    bool hasSaves() const { return saved != nullptr; }
    bool isParentState(GfxState *state) { return saved == state || (saved && saved->isParentState(state)); }

    // Misc
    bool parseBlendMode(Object *obj, GfxBlendMode *mode);

    ReusablePathIterator *getReusablePath() { return new ReusablePathIterator(path); }

private:
    double hDPI, vDPI; // resolution
    double ctm[6]; // coord transform matrix
    double px1, py1, px2, py2; // page corners (user coords)
    double pageWidth, pageHeight; // page size (pixels)
    int rotate; // page rotation angle

    GfxColorSpace *fillColorSpace; // fill color space
    GfxColorSpace *strokeColorSpace; // stroke color space
    GfxColor fillColor; // fill color
    GfxColor strokeColor; // stroke color
    GfxPattern *fillPattern; // fill pattern
    GfxPattern *strokePattern; // stroke pattern
    GfxBlendMode blendMode; // transparency blend mode
    double fillOpacity; // fill opacity
    double strokeOpacity; // stroke opacity
    bool fillOverprint; // fill overprint
    bool strokeOverprint; // stroke overprint
    int overprintMode; // overprint mode
    Function *transfer[4]; // transfer function (entries may be: all
                           //   nullptr = identity; last three nullptr =
                           //   single function; all four non-nullptr =
                           //   R,G,B,gray functions)

    double lineWidth; // line width
    double *lineDash; // line dash
    int lineDashLength;
    double lineDashStart;
    int flatness; // curve flatness
    int lineJoin; // line join style
    int lineCap; // line cap style
    double miterLimit; // line miter limit
    bool strokeAdjust; // stroke adjustment
    bool alphaIsShape; // alpha is shape
    bool textKnockout; // text knockout

    GfxFont *font; // font
    double fontSize; // font size
    double textMat[6]; // text matrix
    double charSpace; // character spacing
    double wordSpace; // word spacing
    double horizScaling; // horizontal scaling
    double leading; // text leading
    double rise; // text rise
    int render; // text rendering mode

    GfxPath *path; // array of path elements
    double curX, curY; // current point (user coords)
    double lineX, lineY; // start of current text line (text coords)

    double clipXMin, clipYMin, // bounding box for clip region
            clipXMax, clipYMax;
    char renderingIntent[32];

    GfxState *saved; // next GfxState on stack

    GfxState(const GfxState *state, bool copyPath);

#ifdef USE_CMS
    GfxLCMSProfilePtr localDisplayProfile;
    std::shared_ptr<GfxColorTransform> XYZ2DisplayTransformRelCol;
    std::shared_ptr<GfxColorTransform> XYZ2DisplayTransformAbsCol;
    std::shared_ptr<GfxColorTransform> XYZ2DisplayTransformSat;
    std::shared_ptr<GfxColorTransform> XYZ2DisplayTransformPerc;
    static GfxLCMSProfilePtr XYZProfile;
#endif

    GfxColorSpace *defaultGrayColorSpace;
    GfxColorSpace *defaultRGBColorSpace;
    GfxColorSpace *defaultCMYKColorSpace;
};

#endif
