//========================================================================
//
// GfxState.cc
//
// 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, 2010 Carlos Garcia Campos <carlosgc@gnome.org>
// Copyright (C) 2006-2020 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2009, 2012 Koji Otani <sho@bbr.jp>
// Copyright (C) 2009, 2011-2016 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2009, 2019 Christian Persch <chpe@gnome.org>
// Copyright (C) 2010 Paweł Wiejacha <pawel.wiejacha@gmail.com>
// Copyright (C) 2010 Christian Feuersänger <cfeuersaenger@googlemail.com>
// Copyright (C) 2011 Andrea Canciani <ranma42@gmail.com>
// Copyright (C) 2012 William Bader <williambader@hotmail.com>
// Copyright (C) 2013 Lu Wang <coolwanglu@gmail.com>
// Copyright (C) 2013 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2013 Fabio D'Urso <fabiodurso@hotmail.it>
// Copyright (C) 2015 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2016 Marek Kasik <mkasik@redhat.com>
// Copyright (C) 2017, 2019 Oliver Sander <oliver.sander@tu-dresden.de>
// 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 Volker Krause <vkrause@kde.org>
// Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2019 LE GARREC Vincent <legarrec.vincent@gmail.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
//
//========================================================================

#include <config.h>

#include <algorithm>
#include <memory>
#include <cstddef>
#include <cmath>
#include <cstring>
#include "goo/gfile.h"
#include "goo/gmem.h"
#include "Error.h"
#include "Object.h"
#include "Array.h"
#include "Page.h"
#include "Gfx.h"
#include "GfxState.h"
#include "GfxState_helpers.h"
#include "GfxFont.h"
#include "GlobalParams.h"
#include "PopplerCache.h"
#include "OutputDev.h"
#include "splash/SplashTypes.h"

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

// Max depth of nested color spaces.  This is used to catch infinite
// loops in the color space object structure.
#define colorSpaceRecursionLimit 8

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

bool Matrix::invertTo(Matrix *other) const
{
  const double det_denominator = determinant();
  if (unlikely(det_denominator == 0)) {
      *other = {1, 0, 0, 1, 0, 0};
      return false;
  }

  const double det = 1 / det_denominator;
  other->m[0] = m[3] * det;
  other->m[1] = -m[1] * det;
  other->m[2] = -m[2] * det;
  other->m[3] = m[0] * det;
  other->m[4] = (m[2] * m[5] - m[3] * m[4]) * det;
  other->m[5] = (m[1] * m[4] - m[0] * m[5]) * det;

  return true;
}

void Matrix::translate(double tx, double ty)
{
  double x0 = tx*m[0] + ty*m[2] + m[4];
  double y0 = tx*m[1] + ty*m[3] + m[5];
  m[4] = x0;
  m[5] = y0;
}

void Matrix::scale(double sx, double sy)
{
  m[0] *= sx;
  m[1] *= sx;
  m[2] *= sy;
  m[3] *= sy;
}

void Matrix::transform(double x, double y, double *tx, double *ty) const
{
  double temp_x, temp_y;

  temp_x = m[0] * x + m[2] * y + m[4];
  temp_y = m[1] * x + m[3] * y + m[5];

  *tx = temp_x;
  *ty = temp_y;
}

// Matrix norm, taken from _cairo_matrix_transformed_circle_major_axis
double Matrix::norm() const
{
  double f, g, h, i, j;

  i = m[0]*m[0] + m[1]*m[1];
  j = m[2]*m[2] + m[3]*m[3];

  f = 0.5 * (i + j);
  g = 0.5 * (i - j);
  h = m[0]*m[2] + m[1]*m[3];

  return sqrt (f + hypot (g, h));
}

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

struct GfxBlendModeInfo {
  const char *name;
  GfxBlendMode mode;
};

static const GfxBlendModeInfo gfxBlendModeNames[] = {
  { "Normal",     gfxBlendNormal },
  { "Compatible", gfxBlendNormal },
  { "Multiply",   gfxBlendMultiply },
  { "Screen",     gfxBlendScreen },
  { "Overlay",    gfxBlendOverlay },
  { "Darken",     gfxBlendDarken },
  { "Lighten",    gfxBlendLighten },
  { "ColorDodge", gfxBlendColorDodge },
  { "ColorBurn",  gfxBlendColorBurn },
  { "HardLight",  gfxBlendHardLight },
  { "SoftLight",  gfxBlendSoftLight },
  { "Difference", gfxBlendDifference },
  { "Exclusion",  gfxBlendExclusion },
  { "Hue",        gfxBlendHue },
  { "Saturation", gfxBlendSaturation },
  { "Color",      gfxBlendColor },
  { "Luminosity", gfxBlendLuminosity }
};

#define nGfxBlendModeNames \
          ((int)((sizeof(gfxBlendModeNames) / sizeof(GfxBlendModeInfo))))
	 
//------------------------------------------------------------------------
// 
// NB: This must match the GfxColorSpaceMode enum defined in
// GfxState.h
static const char *gfxColorSpaceModeNames[] = {
  "DeviceGray",
  "CalGray",
  "DeviceRGB",
  "CalRGB",
  "DeviceCMYK",
  "Lab",
  "ICCBased",
  "Indexed",
  "Separation",
  "DeviceN",
  "Pattern"
};

#define nGfxColorSpaceModes ((sizeof(gfxColorSpaceModeNames) / sizeof(char *)))

#ifdef USE_CMS

static const std::map<unsigned int, unsigned int>::size_type CMSCACHE_LIMIT = 2048;

#include <lcms2.h>
#define LCMS_FLAGS cmsFLAGS_NOOPTIMIZE | cmsFLAGS_BLACKPOINTCOMPENSATION

#define COLOR_PROFILE_DIR "/ColorProfiles/"
#define GLOBAL_COLOR_PROFILE_DIR POPPLER_DATADIR COLOR_PROFILE_DIR

void GfxColorTransform::doTransform(void *in, void *out, unsigned int size) {
  cmsDoTransform(transform, in, out, size);
}

// transformA should be a cmsHTRANSFORM
GfxColorTransform::GfxColorTransform(void *transformA, int cmsIntentA, unsigned int inputPixelTypeA, unsigned int transformPixelTypeA) {
  transform = transformA;
  refCount = 1;
  cmsIntent = cmsIntentA;
  inputPixelType = inputPixelTypeA;
  transformPixelType = transformPixelTypeA;
}

GfxColorTransform::~GfxColorTransform() {
  cmsDeleteTransform(transform);
}

void GfxColorTransform::ref() {
  refCount++;
}

unsigned int GfxColorTransform::unref() {
  return --refCount;
}

static cmsHPROFILE RGBProfile = nullptr;
static GooString *displayProfileName = nullptr; // display profile file Name
static cmsHPROFILE displayProfile = nullptr; // display profile
static unsigned int displayPixelType = 0;
static GfxColorTransform *XYZ2DisplayTransform = nullptr;

// convert color space signature to cmsColor type 
static unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs);
static unsigned int getCMSNChannels(cmsColorSpaceSignature cs);
static cmsHPROFILE loadColorProfile(const char *fileName);

void GfxColorSpace::setDisplayProfile(void *displayProfileA) {
  if (displayProfile != nullptr) {
    error(errInternal, -1, "The display color profile can only be set once before any rendering is done.");
    return;
  }
  displayProfile = displayProfileA;
  if (displayProfile != nullptr) {
    cmsHTRANSFORM transform;
    unsigned int nChannels;

    displayPixelType = getCMSColorSpaceType(cmsGetColorSpace(displayProfile));
    nChannels = getCMSNChannels(cmsGetColorSpace(displayProfile));
    // create transform from XYZ
    cmsHPROFILE XYZProfile = cmsCreateXYZProfile();
    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
	   displayProfile,
	   COLORSPACE_SH(displayPixelType) |
	     CHANNELS_SH(nChannels) | BYTES_SH(1),
	  INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
      error(errSyntaxWarning, -1, "Can't create Lab transform");
    } else {
      XYZ2DisplayTransform = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, displayPixelType);
    }
    cmsCloseProfile(XYZProfile);
  }
}

void GfxColorSpace::setDisplayProfileName(GooString *name) {
  if (displayProfile != nullptr) {
    error(errInternal, -1, "The display color profile can only be set before any rendering is done.");
    return;
  }
  delete displayProfileName;
  displayProfileName = name->copy();
}

cmsHPROFILE GfxColorSpace::getRGBProfile() {
  return RGBProfile;
}

cmsHPROFILE GfxColorSpace::getDisplayProfile() {
  return displayProfile;
}

#endif

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

GfxColorSpace::GfxColorSpace() {
  overprintMask = 0x0f;
  mapping = nullptr;
}

GfxColorSpace::~GfxColorSpace() {
}

GfxColorSpace *GfxColorSpace::parse(GfxResources *res, Object *csObj, OutputDev *out, GfxState *state, int recursion) {
  GfxColorSpace *cs;
  Object obj1;

  if (recursion > colorSpaceRecursionLimit) {
    error(errSyntaxError, -1, "Loop detected in color space objects");
    return nullptr;
  }

  cs = nullptr;
  if (csObj->isName()) {
    if (csObj->isName("DeviceGray") || csObj->isName("G")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultGray");
        if (objCS.isNull()) {
          cs = new GfxDeviceGrayColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceGrayColorSpace();
      }
    } else if (csObj->isName("DeviceRGB") || csObj->isName("RGB")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultRGB");
        if (objCS.isNull()) {
          cs = new GfxDeviceRGBColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceRGBColorSpace();
      }
    } else if (csObj->isName("DeviceCMYK") || csObj->isName("CMYK")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultCMYK");
        if (objCS.isNull()) {
          cs = new GfxDeviceCMYKColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceCMYKColorSpace();
      }
    } else if (csObj->isName("Pattern")) {
      cs = new GfxPatternColorSpace(nullptr);
    } else {
      error(errSyntaxWarning, -1, "Bad color space '{0:s}'", csObj->getName());
    }
  } else if (csObj->isArray() && csObj->arrayGetLength() > 0) {
    obj1 = csObj->arrayGet(0);
    if (obj1.isName("DeviceGray") || obj1.isName("G")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultGray");
        if (objCS.isNull()) {
          cs = new GfxDeviceGrayColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceGrayColorSpace();
      }
    } else if (obj1.isName("DeviceRGB") || obj1.isName("RGB")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultRGB");
        if (objCS.isNull()) {
          cs = new GfxDeviceRGBColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceRGBColorSpace();
      }
    } else if (obj1.isName("DeviceCMYK") || obj1.isName("CMYK")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultCMYK");
        if (objCS.isNull()) {
          cs = new GfxDeviceCMYKColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceCMYKColorSpace();
      }
    } else if (obj1.isName("CalGray")) {
      cs = GfxCalGrayColorSpace::parse(csObj->getArray(), state);
    } else if (obj1.isName("CalRGB")) {
      cs = GfxCalRGBColorSpace::parse(csObj->getArray(), state);
    } else if (obj1.isName("Lab")) {
      cs = GfxLabColorSpace::parse(csObj->getArray(), state);
    } else if (obj1.isName("ICCBased")) {
      cs = GfxICCBasedColorSpace::parse(csObj->getArray(), out, state, recursion);
    } else if (obj1.isName("Indexed") || obj1.isName("I")) {
      cs = GfxIndexedColorSpace::parse(res, csObj->getArray(), out, state, recursion);
    } else if (obj1.isName("Separation")) {
      cs = GfxSeparationColorSpace::parse(res, csObj->getArray(), out, state, recursion);
    } else if (obj1.isName("DeviceN")) {
      cs = GfxDeviceNColorSpace::parse(res, csObj->getArray(), out, state, recursion);
    } else if (obj1.isName("Pattern")) {
      cs = GfxPatternColorSpace::parse(res, csObj->getArray(), out, state, recursion);
    } else {
      error(errSyntaxWarning, -1, "Bad color space");
    }
  } else if (csObj->isDict()) {
    obj1 = csObj->dictLookup("ColorSpace");
    if (obj1.isName("DeviceGray")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultGray");
        if (objCS.isNull()) {
          cs = new GfxDeviceGrayColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceGrayColorSpace();
      }
    } else if (obj1.isName("DeviceRGB")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultRGB");
        if (objCS.isNull()) {
          cs = new GfxDeviceRGBColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceRGBColorSpace();
      }
    } else if (obj1.isName("DeviceCMYK")) {
      if (res != nullptr) {
        Object objCS = res->lookupColorSpace("DefaultCMYK");
        if (objCS.isNull()) {
          cs = new GfxDeviceCMYKColorSpace();
        } else {
          cs = GfxColorSpace::parse(nullptr, &objCS, out, state);
        }
      } else {
        cs = new GfxDeviceCMYKColorSpace();
      }
    } else {
      error(errSyntaxWarning, -1, "Bad color space dict'");
    }
  } else {
    error(errSyntaxWarning, -1, "Bad color space - expected name or array or dict");
  }
  return cs;
}

void GfxColorSpace::createMapping(std::vector<GfxSeparationColorSpace*> *separationList, int maxSepComps) {
  return;
}

void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange,
				     int maxImgPixel) const {
  int i;

  for (i = 0; i < getNComps(); ++i) {
    decodeLow[i] = 0;
    decodeRange[i] = 1;
  }
}

int GfxColorSpace::getNumColorSpaceModes() {
  return nGfxColorSpaceModes;
}

const char *GfxColorSpace::getColorSpaceModeName(int idx) {
  return gfxColorSpaceModeNames[idx];
}

#ifdef USE_CMS
cmsHPROFILE loadColorProfile(const char *fileName)
{
  cmsHPROFILE hp = nullptr;
  FILE *fp;

  if (fileName[0] == '/') {
    // full path
    // check if open the file
    if ((fp = openFile(fileName,"r")) != nullptr) {
      fclose(fp);
      hp = cmsOpenProfileFromFile(fileName,"r");
    }
    return hp;
  }
  // try to load from global directory
  GooString *path = new GooString(GLOBAL_COLOR_PROFILE_DIR);
  path->append(fileName);
  // check if open the file
  if ((fp = openFile(path->c_str(),"r")) != nullptr) {
    fclose(fp);
    hp = cmsOpenProfileFromFile(path->c_str(),"r");
  }
  delete path;
  return hp;
}

static void CMSError(cmsContext /*contextId*/, cmsUInt32Number /*ecode*/, const char *text)
{
    error(errSyntaxWarning, -1, "{0:s}", text);
}

int GfxColorSpace::setupColorProfiles()
{
  static bool initialized = false;
  cmsHTRANSFORM transform;
  unsigned int nChannels;

  // do only once
  if (initialized) return 0;
  initialized = true;

  // set error handlor
  cmsSetLogErrorHandler(CMSError);

  if (displayProfile == nullptr) {
    // load display profile if it was not already loaded.
    if (displayProfileName == nullptr) {
      displayProfile = loadColorProfile("display.icc");
    } else if (displayProfileName->getLength() > 0) {
      displayProfile = loadColorProfile(displayProfileName->c_str());
    }
  }
  // load RGB profile
  RGBProfile = loadColorProfile("RGB.icc");
  if (RGBProfile == nullptr) {
    /* use built in sRGB profile */
    RGBProfile = cmsCreate_sRGBProfile();
  }
  // create transforms
  if (displayProfile != nullptr) {
    displayPixelType = getCMSColorSpaceType(cmsGetColorSpace(displayProfile));
    nChannels = getCMSNChannels(cmsGetColorSpace(displayProfile));
    // create transform from XYZ
    cmsHPROFILE XYZProfile = cmsCreateXYZProfile();
    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
	   displayProfile,
	   COLORSPACE_SH(displayPixelType) |
	     CHANNELS_SH(nChannels) | BYTES_SH(1),
	  INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
      error(errSyntaxWarning, -1, "Can't create Lab transform");
    } else {
      XYZ2DisplayTransform = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, displayPixelType);
    }
    cmsCloseProfile(XYZProfile);
  }
  return 0;
}

unsigned int getCMSColorSpaceType(cmsColorSpaceSignature cs)
{
    switch (cs) {
    case cmsSigXYZData:
      return PT_XYZ;
      break;
    case cmsSigLabData:
      return PT_Lab;
      break;
    case cmsSigLuvData:
      return PT_YUV;
      break;
    case cmsSigYCbCrData:
      return PT_YCbCr;
      break;
    case cmsSigYxyData:
      return PT_Yxy;
      break;
    case cmsSigRgbData:
      return PT_RGB;
      break;
    case cmsSigGrayData:
      return PT_GRAY;
      break;
    case cmsSigHsvData:
      return PT_HSV;
      break;
    case cmsSigHlsData:
      return PT_HLS;
      break;
    case cmsSigCmykData:
      return PT_CMYK;
      break;
    case cmsSigCmyData:
      return PT_CMY;
      break;
    case cmsSig2colorData:
    case cmsSig3colorData:
    case cmsSig4colorData:
    case cmsSig5colorData:
    case cmsSig6colorData:
    case cmsSig7colorData:
    case cmsSig8colorData:
    case cmsSig9colorData:
    case cmsSig10colorData:
    case cmsSig11colorData:
    case cmsSig12colorData:
    case cmsSig13colorData:
    case cmsSig14colorData:
    case cmsSig15colorData:
    default:
      break;
    }
    return PT_RGB;
}

unsigned int getCMSNChannels(cmsColorSpaceSignature cs)
{
    switch (cs) {
    case cmsSigXYZData:
    case cmsSigLuvData:
    case cmsSigLabData:
    case cmsSigYCbCrData:
    case cmsSigYxyData:
    case cmsSigRgbData:
    case cmsSigHsvData:
    case cmsSigHlsData:
    case cmsSigCmyData:
    case cmsSig3colorData:
      return 3;
      break;
    case cmsSigGrayData:
      return 1;
      break;
    case cmsSigCmykData:
    case cmsSig4colorData:
      return 4;
      break;
    case cmsSig2colorData:
      return 2;
      break;
    case cmsSig5colorData:
      return 5;
      break;
    case cmsSig6colorData:
      return 6;
      break;
    case cmsSig7colorData:
      return 7;
      break;
    case cmsSig8colorData:
      return 8;
      break;
    case cmsSig9colorData:
      return 9;
      break;
    case cmsSig10colorData:
      return 10;
      break;
    case cmsSig11colorData:
      return 11;
      break;
    case cmsSig12colorData:
      return 12;
      break;
    case cmsSig13colorData:
      return 13;
      break;
    case cmsSig14colorData:
      return 14;
      break;
    case cmsSig15colorData:
      return 15;
    default:
      break;
    }
    return 3;
}
#endif

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

GfxDeviceGrayColorSpace::GfxDeviceGrayColorSpace() {
}

GfxDeviceGrayColorSpace::~GfxDeviceGrayColorSpace() {
}

GfxColorSpace *GfxDeviceGrayColorSpace::copy() const {
  return new GfxDeviceGrayColorSpace();
}

void GfxDeviceGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  *gray = clip01(color->c[0]);
}

void GfxDeviceGrayColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length) {
  memcpy (out, in, length);
}

void GfxDeviceGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  rgb->r = rgb->g = rgb->b = clip01(color->c[0]);
}

void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned int *out,
					 int length) {
  int i;

  for (i = 0; i < length; i++)
    out[i] = (in[i] << 16) | (in[i] << 8) | (in[i] << 0);
}

void GfxDeviceGrayColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) {
  for (int i = 0; i < length; i++) {
    *out++ = in[i];
    *out++ = in[i];
    *out++ = in[i];
  }
}

void GfxDeviceGrayColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) {
  for (int i = 0; i < length; i++) {
    *out++ = in[i];
    *out++ = in[i];
    *out++ = in[i];
    *out++ = 255;
  }
}

void GfxDeviceGrayColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) {
  for (int i = 0; i < length; i++) {
    *out++ = 0;
    *out++ = 0;
    *out++ = 0;
    *out++ = in[i];
  }
}

void GfxDeviceGrayColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) {
  for (int i = 0; i < length; i++) {
    for (int j = 0; j < SPOT_NCOMPS+4; j++)
      out[j] = 0;
    out[4] = in[i];
    out += (SPOT_NCOMPS+4);
  }
}

void GfxDeviceGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  cmyk->c = cmyk->m = cmyk->y = 0;
  cmyk->k = clip01(gfxColorComp1 - color->c[0]);
}

void GfxDeviceGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  clearGfxColor(deviceN);
  deviceN->c[3] = clip01(gfxColorComp1 - color->c[0]);
}

void GfxDeviceGrayColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = 0;
}

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

GfxCalGrayColorSpace::GfxCalGrayColorSpace() {
  whiteX = whiteY = whiteZ = 1;
  blackX = blackY = blackZ = 0;
  gamma = 1;
}

GfxCalGrayColorSpace::~GfxCalGrayColorSpace() {
#ifdef USE_CMS
  if (transform != nullptr) {
    if (transform->unref() == 0) delete transform;
  }
#endif
}

GfxColorSpace *GfxCalGrayColorSpace::copy() const {
  GfxCalGrayColorSpace *cs;

  cs = new GfxCalGrayColorSpace();
  cs->whiteX = whiteX;
  cs->whiteY = whiteY;
  cs->whiteZ = whiteZ;
  cs->blackX = blackX;
  cs->blackY = blackY;
  cs->blackZ = blackZ;
  cs->gamma = gamma;
  cs->kr = kr;
  cs->kg = kg;
  cs->kb = kb;
#ifdef USE_CMS
  cs->transform = transform;
  if (transform != nullptr) transform->ref();
#endif
  return cs;
}

// This is the inverse of MatrixLMN in Example 4.10 from the PostScript
// Language Reference, Third Edition.
static const double xyzrgb[3][3] = {
  {  3.240449, -1.537136, -0.498531 },
  { -0.969265,  1.876011,  0.041556 },
  {  0.055643, -0.204026,  1.057229 }
};

GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, GfxState *state) {
  GfxCalGrayColorSpace *cs;
  Object obj1, obj2;

  obj1 = arr->get(1);
  if (!obj1.isDict()) {
    error(errSyntaxWarning, -1, "Bad CalGray color space");
    return nullptr;
  }
  cs = new GfxCalGrayColorSpace();
  obj2 = obj1.dictLookup("WhitePoint");
  if (obj2.isArray() && obj2.arrayGetLength() == 3) {
    cs->whiteX = obj2.arrayGet(0).getNumWithDefaultValue(1);
    cs->whiteY = obj2.arrayGet(1).getNumWithDefaultValue(1);
    cs->whiteZ = obj2.arrayGet(2).getNumWithDefaultValue(1);
  }
  obj2 = obj1.dictLookup("BlackPoint");
  if (obj2.isArray() && obj2.arrayGetLength() == 3) {
    cs->blackX = obj2.arrayGet(0).getNumWithDefaultValue(0);
    cs->blackY = obj2.arrayGet(1).getNumWithDefaultValue(0);
    cs->blackZ = obj2.arrayGet(2).getNumWithDefaultValue(0);
  }

  cs->gamma = obj1.dictLookup("Gamma").getNumWithDefaultValue(1);

  cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX +
		xyzrgb[0][1] * cs->whiteY +
		xyzrgb[0][2] * cs->whiteZ);
  cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX +
		xyzrgb[1][1] * cs->whiteY +
		xyzrgb[1][2] * cs->whiteZ);
  cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX +
		xyzrgb[2][1] * cs->whiteY +
		xyzrgb[2][2] * cs->whiteZ);
#ifdef USE_CMS
  cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : XYZ2DisplayTransform;
  if (cs->transform != nullptr) cs->transform->ref();
#endif
  return cs;
}

// convert CalGray to media XYZ color space
// (not multiply by the white point)
void GfxCalGrayColorSpace::getXYZ(const GfxColor *color,
  double *pX, double *pY, double *pZ) const {
  const double A = colToDbl(color->c[0]);
  const double xyzColor = pow(A,gamma);
  *pX = xyzColor;
  *pY = xyzColor;
  *pZ = xyzColor;
}

void GfxCalGrayColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  GfxRGB rgb;

#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
    unsigned char out[gfxColorMaxComps];
    double in[gfxColorMaxComps];
    double X, Y, Z;
    
    getXYZ(color,&X,&Y,&Z);
    in[0] = clip01(X);
    in[1] = clip01(Y);
    in[2] = clip01(Z);
    transform->doTransform(in,out,1);
    *gray = byteToCol(out[0]);
    return;
  }
#endif
  getRGB(color, &rgb);
  *gray = clip01((GfxColorComp)(0.299 * rgb.r +
				0.587 * rgb.g +
				0.114 * rgb.b + 0.5));
}

void GfxCalGrayColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  double X, Y, Z;
  double r, g, b;

  getXYZ(color,&X,&Y,&Z);
#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
    unsigned char out[gfxColorMaxComps];
    double in[gfxColorMaxComps];
    
    in[0] = clip01(X);
    in[1] = clip01(Y);
    in[2] = clip01(Z);
    transform->doTransform(in,out,1);
    rgb->r = byteToCol(out[0]);
    rgb->g = byteToCol(out[1]);
    rgb->b = byteToCol(out[2]);
    return;
  }
#endif
  X *= whiteX;
  Y *= whiteY;
  Z *= whiteZ;
  // convert XYZ to RGB, including gamut mapping and gamma correction
  r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
  g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
  b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
  rgb->r = dblToCol(sqrt(clip01(r * kr)));
  rgb->g = dblToCol(sqrt(clip01(g * kg)));
  rgb->b = dblToCol(sqrt(clip01(b * kb)));
}

void GfxCalGrayColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  GfxRGB rgb;
  GfxColorComp c, m, y, k;

#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
    double in[gfxColorMaxComps];
    unsigned char out[gfxColorMaxComps];
    double X, Y, Z;
    
    getXYZ(color,&X,&Y,&Z);
    in[0] = clip01(X);
    in[1] = clip01(Y);
    in[2] = clip01(Z);
    
    transform->doTransform(in,out,1);
    cmyk->c = byteToCol(out[0]);
    cmyk->m = byteToCol(out[1]);
    cmyk->y = byteToCol(out[2]);
    cmyk->k = byteToCol(out[3]);
    return;
  }
#endif
  getRGB(color, &rgb);
  c = clip01(gfxColorComp1 - rgb.r);
  m = clip01(gfxColorComp1 - rgb.g);
  y = clip01(gfxColorComp1 - rgb.b);
  k = c;
  if (m < k) {
    k = m;
  }
  if (y < k) {
    k = y;
  }
  cmyk->c = c - k;
  cmyk->m = m - k;
  cmyk->y = y - k;
  cmyk->k = k;
}

void GfxCalGrayColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  GfxCMYK cmyk;
  clearGfxColor(deviceN);
  getCMYK(color, &cmyk);
  deviceN->c[0] = cmyk.c;
  deviceN->c[1] = cmyk.m;
  deviceN->c[2] = cmyk.y;
  deviceN->c[3] = cmyk.k;
}

void GfxCalGrayColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = 0;
}

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

GfxDeviceRGBColorSpace::GfxDeviceRGBColorSpace() {
}

GfxDeviceRGBColorSpace::~GfxDeviceRGBColorSpace() {
}

GfxColorSpace *GfxDeviceRGBColorSpace::copy() const {
  return new GfxDeviceRGBColorSpace();
}

void GfxDeviceRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  *gray = clip01((GfxColorComp)(0.3 * color->c[0] +
		 0.59 * color->c[1] +
		 0.11 * color->c[2] + 0.5));
}

void GfxDeviceRGBColorSpace::getGrayLine(unsigned char *in, unsigned char *out, int length) {
  int i;

  for (i = 0; i < length; i++) {
    out[i] = 
      (in[i * 3 + 0] * 19595 + 
       in[i * 3 + 1] * 38469 + 
       in[i * 3 + 2] * 7472) / 65536;
  }
}

void GfxDeviceRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  rgb->r = clip01(color->c[0]);
  rgb->g = clip01(color->c[1]);
  rgb->b = clip01(color->c[2]);
}

void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned int *out,
					int length) {
  unsigned char *p;
  int i;

  for (i = 0, p = in; i < length; i++, p += 3)
    out[i] = (p[0] << 16) | (p[1] << 8) | (p[2] << 0);
}

void GfxDeviceRGBColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) {
  for (int i = 0; i < length; i++) {
    *out++ = *in++;
    *out++ = *in++;
    *out++ = *in++;
  }
}

void GfxDeviceRGBColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) {
  for (int i = 0; i < length; i++) {
    *out++ = *in++;
    *out++ = *in++;
    *out++ = *in++;
    *out++ = 255;
  }
}

void GfxDeviceRGBColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) {
  GfxColorComp c, m, y, k;

  for (int i = 0; i < length; i++) {
    c = byteToCol(255 - *in++);
    m = byteToCol(255 - *in++);
    y = byteToCol(255 - *in++);
    k = c;
    if (m < k) {
      k = m;
    }
    if (y < k) {
      k = y;
    }
    *out++ = colToByte(c - k);
    *out++ = colToByte(m - k);
    *out++ = colToByte(y - k);
    *out++ = colToByte(k);
  }
}

void GfxDeviceRGBColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) {
  GfxColorComp c, m, y, k;

  for (int i = 0; i < length; i++) {
    for (int j = 0; j < SPOT_NCOMPS+4; j++) 
      out[j] = 0;
    c = byteToCol(255 - *in++);
    m = byteToCol(255 - *in++);
    y = byteToCol(255 - *in++);
    k = c;
    if (m < k) {
      k = m;
    }
    if (y < k) {
      k = y;
    }
    out[0] = colToByte(c - k);
    out[1] = colToByte(m - k);
    out[2] = colToByte(y - k);
    out[3] = colToByte(k);
    out += (SPOT_NCOMPS+4);
  }
}

void GfxDeviceRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  GfxColorComp c, m, y, k;

  c = clip01(gfxColorComp1 - color->c[0]);
  m = clip01(gfxColorComp1 - color->c[1]);
  y = clip01(gfxColorComp1 - color->c[2]);
  k = c;
  if (m < k) {
    k = m;
  }
  if (y < k) {
    k = y;
  }
  cmyk->c = c - k;
  cmyk->m = m - k;
  cmyk->y = y - k;
  cmyk->k = k;
}

void GfxDeviceRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  GfxCMYK cmyk;
  clearGfxColor(deviceN);
  getCMYK(color, &cmyk);
  deviceN->c[0] = cmyk.c;
  deviceN->c[1] = cmyk.m;
  deviceN->c[2] = cmyk.y;
  deviceN->c[3] = cmyk.k;
}

void GfxDeviceRGBColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = 0;
  color->c[1] = 0;
  color->c[2] = 0;
}

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

GfxCalRGBColorSpace::GfxCalRGBColorSpace() {
  whiteX = whiteY = whiteZ = 1;
  blackX = blackY = blackZ = 0;
  gammaR = gammaG = gammaB = 1;
  mat[0] = 1; mat[1] = 0; mat[2] = 0;
  mat[3] = 0; mat[4] = 1; mat[5] = 0;
  mat[6] = 0; mat[7] = 0; mat[8] = 1;
}

GfxCalRGBColorSpace::~GfxCalRGBColorSpace() {
#ifdef USE_CMS
  if (transform != nullptr) {
    if (transform->unref() == 0) delete transform;
  }
#endif
}

GfxColorSpace *GfxCalRGBColorSpace::copy() const {
  GfxCalRGBColorSpace *cs;
  int i;

  cs = new GfxCalRGBColorSpace();
  cs->whiteX = whiteX;
  cs->whiteY = whiteY;
  cs->whiteZ = whiteZ;
  cs->blackX = blackX;
  cs->blackY = blackY;
  cs->blackZ = blackZ;
  cs->gammaR = gammaR;
  cs->gammaG = gammaG;
  cs->gammaB = gammaB;
  cs->kr = kr;
  cs->kg = kg;
  cs->kb = kb;
  for (i = 0; i < 9; ++i) {
    cs->mat[i] = mat[i];
  }
#ifdef USE_CMS
  cs->transform = transform;
  if (transform != nullptr) transform->ref();
#endif
  return cs;
}

GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr, GfxState *state) {
  GfxCalRGBColorSpace *cs;
  Object obj1, obj2;
  int i;

  obj1 = arr->get(1);
  if (!obj1.isDict()) {
    error(errSyntaxWarning, -1, "Bad CalRGB color space");
    return nullptr;
  }
  cs = new GfxCalRGBColorSpace();
  obj2 = obj1.dictLookup("WhitePoint");
  if (obj2.isArray() && obj2.arrayGetLength() == 3) {
    cs->whiteX = obj2.arrayGet(0).getNumWithDefaultValue(1);
    cs->whiteY = obj2.arrayGet(1).getNumWithDefaultValue(1);
    cs->whiteZ = obj2.arrayGet(2).getNumWithDefaultValue(1);
  }
  obj2 = obj1.dictLookup("BlackPoint");
  if (obj2.isArray() && obj2.arrayGetLength() == 3) {
    cs->blackX = obj2.arrayGet(0).getNumWithDefaultValue(0);
    cs->blackY = obj2.arrayGet(1).getNumWithDefaultValue(0);
    cs->blackZ = obj2.arrayGet(2).getNumWithDefaultValue(0);
  }
  obj2 = obj1.dictLookup("Gamma");
  if (obj2.isArray() && obj2.arrayGetLength() == 3) {
    cs->gammaR = obj2.arrayGet(0).getNumWithDefaultValue(1);
    cs->gammaG = obj2.arrayGet(1).getNumWithDefaultValue(1);
    cs->gammaB = obj2.arrayGet(2).getNumWithDefaultValue(1);
  }
  obj2 = obj1.dictLookup("Matrix");
  if (obj2.isArray() && obj2.arrayGetLength() == 9) {
    for (i = 0; i < 9; ++i) {
      Object obj3 = obj2.arrayGet(i);
      if (likely(obj3.isNum()))
        cs->mat[i] = obj3.getNum();
    }
  }

  cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX +
		xyzrgb[0][1] * cs->whiteY +
		xyzrgb[0][2] * cs->whiteZ);
  cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX +
		xyzrgb[1][1] * cs->whiteY +
		xyzrgb[1][2] * cs->whiteZ);
  cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX +
		xyzrgb[2][1] * cs->whiteY +
		xyzrgb[2][2] * cs->whiteZ);

#ifdef USE_CMS
  cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : XYZ2DisplayTransform;
  if (cs->transform != nullptr) cs->transform->ref();
#endif
  return cs;
}

// convert CalRGB to XYZ color space
void GfxCalRGBColorSpace::getXYZ(const GfxColor *color,
  double *pX, double *pY, double *pZ) const {
  double A, B, C;

  A = pow(colToDbl(color->c[0]), gammaR);
  B = pow(colToDbl(color->c[1]), gammaG);
  C = pow(colToDbl(color->c[2]), gammaB);
  *pX = mat[0] * A + mat[3] * B + mat[6] * C;
  *pY = mat[1] * A + mat[4] * B + mat[7] * C;
  *pZ = mat[2] * A + mat[5] * B + mat[8] * C;
}

void GfxCalRGBColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  GfxRGB rgb;

#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
    unsigned char out[gfxColorMaxComps];
    double in[gfxColorMaxComps];
    double X, Y, Z;
    
    getXYZ(color,&X,&Y,&Z);
    in[0] = clip01(X);
    in[1] = clip01(Y);
    in[2] = clip01(Z);
    transform->doTransform(in,out,1);
    *gray = byteToCol(out[0]);
    return;
  }
#endif
  getRGB(color, &rgb);
  *gray = clip01((GfxColorComp)(0.299 * rgb.r +
				0.587 * rgb.g +
				0.114 * rgb.b + 0.5));
}

void GfxCalRGBColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  double X, Y, Z;
  double r, g, b;

  getXYZ(color,&X,&Y,&Z);
#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
    unsigned char out[gfxColorMaxComps];
    double in[gfxColorMaxComps];
    
    in[0] = clip01(X/whiteX);
    in[1] = clip01(Y/whiteY);
    in[2] = clip01(Z/whiteZ);
    transform->doTransform(in,out,1);
    rgb->r = byteToCol(out[0]);
    rgb->g = byteToCol(out[1]);
    rgb->b = byteToCol(out[2]);
    return;
  }
#endif
  // convert XYZ to RGB, including gamut mapping and gamma correction
  r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
  g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
  b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
  rgb->r = dblToCol(sqrt(clip01(r)));
  rgb->g = dblToCol(sqrt(clip01(g)));
  rgb->b = dblToCol(sqrt(clip01(b)));
}

void GfxCalRGBColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  GfxRGB rgb;
  GfxColorComp c, m, y, k;

#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
    double in[gfxColorMaxComps];
    unsigned char out[gfxColorMaxComps];
    double X, Y, Z;
    
    getXYZ(color,&X,&Y,&Z);
    in[0] = clip01(X);
    in[1] = clip01(Y);
    in[2] = clip01(Z);
    transform->doTransform(in,out,1);
    cmyk->c = byteToCol(out[0]);
    cmyk->m = byteToCol(out[1]);
    cmyk->y = byteToCol(out[2]);
    cmyk->k = byteToCol(out[3]);
    return;
  }
#endif
  getRGB(color, &rgb);
  c = clip01(gfxColorComp1 - rgb.r);
  m = clip01(gfxColorComp1 - rgb.g);
  y = clip01(gfxColorComp1 - rgb.b);
  k = c;
  if (m < k) {
    k = m;
  }
  if (y < k) {
    k = y;
  }
  cmyk->c = c - k;
  cmyk->m = m - k;
  cmyk->y = y - k;
  cmyk->k = k;
}

void GfxCalRGBColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  GfxCMYK cmyk;
  clearGfxColor(deviceN);
  getCMYK(color, &cmyk);
  deviceN->c[0] = cmyk.c;
  deviceN->c[1] = cmyk.m;
  deviceN->c[2] = cmyk.y;
  deviceN->c[3] = cmyk.k;
}

void GfxCalRGBColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = 0;
  color->c[1] = 0;
  color->c[2] = 0;
}

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

GfxDeviceCMYKColorSpace::GfxDeviceCMYKColorSpace() {
}

GfxDeviceCMYKColorSpace::~GfxDeviceCMYKColorSpace() {
}

GfxColorSpace *GfxDeviceCMYKColorSpace::copy() const {
  return new GfxDeviceCMYKColorSpace();
}

void GfxDeviceCMYKColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  *gray = clip01((GfxColorComp)(gfxColorComp1 - color->c[3]
				- 0.3  * color->c[0]
				- 0.59 * color->c[1]
				- 0.11 * color->c[2] + 0.5));
}

void GfxDeviceCMYKColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  double c, m, y, k, c1, m1, y1, k1, r, g, b;
    
  c = colToDbl(color->c[0]);
  m = colToDbl(color->c[1]);
  y = colToDbl(color->c[2]);
  k = colToDbl(color->c[3]);
  c1 = 1 - c;
  m1 = 1 - m;
  y1 = 1 - y;
  k1 = 1 - k;
  cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
  rgb->r = clip01(dblToCol(r));
  rgb->g = clip01(dblToCol(g));
  rgb->b = clip01(dblToCol(b));
}

static inline void GfxDeviceCMYKColorSpacegetRGBLineHelper(unsigned char *&in, double &r, double &g, double &b)
{
  double c, m, y, k, c1, m1, y1, k1;
  
  c = byteToDbl(*in++);
  m = byteToDbl(*in++);
  y = byteToDbl(*in++);
  k = byteToDbl(*in++);
  c1 = 1 - c;
  m1 = 1 - m;
  y1 = 1 - y;
  k1 = 1 - k;
  cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
}

void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length)
{
  double r, g, b;
  for (int i = 0; i < length; i++) {
    GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
    *out++ = (dblToByte(clip01(r)) << 16) | (dblToByte(clip01(g)) << 8) | dblToByte(clip01(b));
  }
}

void GfxDeviceCMYKColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
{
  double r, g, b;
  
  for (int i = 0; i < length; i++) {
    GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
    *out++ = dblToByte(clip01(r));
    *out++ = dblToByte(clip01(g));
    *out++ = dblToByte(clip01(b));
  }
}

void GfxDeviceCMYKColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
{
  double r, g, b;
  
  for (int i = 0; i < length; i++) {
    GfxDeviceCMYKColorSpacegetRGBLineHelper(in, r, g, b);
    *out++ = dblToByte(clip01(r));
    *out++ = dblToByte(clip01(g));
    *out++ = dblToByte(clip01(b));
    *out++ = 255;
  }
}

void GfxDeviceCMYKColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length)
{
  for (int i = 0; i < length; i++) {
    *out++ = *in++;
    *out++ = *in++;
    *out++ = *in++;
    *out++ = *in++;
  }
}

void GfxDeviceCMYKColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length)
{
  for (int i = 0; i < length; i++) {
    for (int j = 0; j < SPOT_NCOMPS+4; j++)
      out[j] = 0;
    out[0] = *in++;
    out[1] = *in++;
    out[2] = *in++;
    out[3] = *in++;
    out += (SPOT_NCOMPS+4);
  }
}

void GfxDeviceCMYKColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  cmyk->c = clip01(color->c[0]);
  cmyk->m = clip01(color->c[1]);
  cmyk->y = clip01(color->c[2]);
  cmyk->k = clip01(color->c[3]);
}

void GfxDeviceCMYKColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  clearGfxColor(deviceN);
  deviceN->c[0] = clip01(color->c[0]);
  deviceN->c[1] = clip01(color->c[1]);
  deviceN->c[2] = clip01(color->c[2]);
  deviceN->c[3] = clip01(color->c[3]);
}

void GfxDeviceCMYKColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = 0;
  color->c[1] = 0;
  color->c[2] = 0;
  color->c[3] = gfxColorComp1;
}

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

GfxLabColorSpace::GfxLabColorSpace() {
  whiteX = whiteY = whiteZ = 1;
  blackX = blackY = blackZ = 0;
  aMin = bMin = -100;
  aMax = bMax = 100;
}

GfxLabColorSpace::~GfxLabColorSpace() {
#ifdef USE_CMS
  if (transform != nullptr) {
    if (transform->unref() == 0) delete transform;
  }
#endif
}

GfxColorSpace *GfxLabColorSpace::copy() const {
  GfxLabColorSpace *cs;

  cs = new GfxLabColorSpace();
  cs->whiteX = whiteX;
  cs->whiteY = whiteY;
  cs->whiteZ = whiteZ;
  cs->blackX = blackX;
  cs->blackY = blackY;
  cs->blackZ = blackZ;
  cs->aMin = aMin;
  cs->aMax = aMax;
  cs->bMin = bMin;
  cs->bMax = bMax;
  cs->kr = kr;
  cs->kg = kg;
  cs->kb = kb;
#ifdef USE_CMS
  cs->transform = transform;
  if (transform != nullptr) transform->ref();
#endif
  return cs;
}

GfxColorSpace *GfxLabColorSpace::parse(Array *arr, GfxState *state) {
  GfxLabColorSpace *cs;
  Object obj1, obj2;

  obj1 = arr->get(1);
  if (!obj1.isDict()) {
    error(errSyntaxWarning, -1, "Bad Lab color space");
    return nullptr;
  }
  cs = new GfxLabColorSpace();
  bool ok = true;
  obj2 = obj1.dictLookup("WhitePoint");
  if (obj2.isArray() && obj2.arrayGetLength() == 3) {
    cs->whiteX = obj2.arrayGet(0).getNum(&ok);
    cs->whiteY = obj2.arrayGet(1).getNum(&ok);
    cs->whiteZ = obj2.arrayGet(2).getNum(&ok);
  }
  obj2 = obj1.dictLookup("BlackPoint");
  if (obj2.isArray() && obj2.arrayGetLength() == 3) {
    cs->blackX = obj2.arrayGet(0).getNum(&ok);
    cs->blackY = obj2.arrayGet(1).getNum(&ok);
    cs->blackZ = obj2.arrayGet(2).getNum(&ok);
  }
  obj2 = obj1.dictLookup("Range");
  if (obj2.isArray() && obj2.arrayGetLength() == 4) {
    cs->aMin = obj2.arrayGet(0).getNum(&ok);
    cs->aMax = obj2.arrayGet(1).getNum(&ok);
    cs->bMin = obj2.arrayGet(2).getNum(&ok);
    cs->bMax = obj2.arrayGet(3).getNum(&ok);
  }

  if (!ok) {
      error(errSyntaxWarning, -1, "Bad Lab color space");
#ifdef USE_CMS
      cs->transform = nullptr;
#endif
      delete cs;
      return nullptr;
  }

  cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX +
		xyzrgb[0][1] * cs->whiteY +
		xyzrgb[0][2] * cs->whiteZ);
  cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX +
		xyzrgb[1][1] * cs->whiteY +
		xyzrgb[1][2] * cs->whiteZ);
  cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX +
		xyzrgb[2][1] * cs->whiteY +
		xyzrgb[2][2] * cs->whiteZ);

#ifdef USE_CMS
  cs->transform = (state != nullptr) ? state->getXYZ2DisplayTransform() : XYZ2DisplayTransform;
  if (cs->transform != nullptr) cs->transform->ref();
#endif
  return cs;
}

void GfxLabColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  GfxRGB rgb;

#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
    unsigned char out[gfxColorMaxComps];
    double in[gfxColorMaxComps];
    
    getXYZ(color, &in[0], &in[1], &in[2]);
    transform->doTransform(in,out,1);
    *gray = byteToCol(out[0]);
    return;
  }
#endif
  getRGB(color, &rgb);
  *gray = clip01((GfxColorComp)(0.299 * rgb.r +
				0.587 * rgb.g +
				0.114 * rgb.b + 0.5));
}

// convert L*a*b* to media XYZ color space
// (not multiply by the white point)
void GfxLabColorSpace::getXYZ(const GfxColor *color,
  double *pX, double *pY, double *pZ) const {
  double X, Y, Z;
  double t1, t2;

  t1 = (colToDbl(color->c[0]) + 16) / 116;
  t2 = t1 + colToDbl(color->c[1]) / 500;
  if (t2 >= (6.0 / 29.0)) {
    X = t2 * t2 * t2;
  } else {
    X = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
  }
  if (t1 >= (6.0 / 29.0)) {
    Y = t1 * t1 * t1;
  } else {
    Y = (108.0 / 841.0) * (t1 - (4.0 / 29.0));
  }
  t2 = t1 - colToDbl(color->c[2]) / 200;
  if (t2 >= (6.0 / 29.0)) {
    Z = t2 * t2 * t2;
  } else {
    Z = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
  }
  *pX = X;
  *pY = Y;
  *pZ = Z;
}

void GfxLabColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  double X, Y, Z;

  getXYZ(color, &X, &Y, &Z);
#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
    unsigned char out[gfxColorMaxComps];
    double in[gfxColorMaxComps];
    
    in[0] = clip01(X);
    in[1] = clip01(Y);
    in[2] = clip01(Z);
    transform->doTransform(in,out,1);
    rgb->r = byteToCol(out[0]);
    rgb->g = byteToCol(out[1]);
    rgb->b = byteToCol(out[2]);
    return;
  } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
    unsigned char out[gfxColorMaxComps];
    double in[gfxColorMaxComps];
    double c, m, y, k, c1, m1, y1, k1, r, g, b;

    in[0] = clip01(X);
    in[1] = clip01(Y);
    in[2] = clip01(Z);
    transform->doTransform(in,out,1);
    c = byteToDbl(out[0]);
    m = byteToDbl(out[1]);
    y = byteToDbl(out[2]);
    k = byteToDbl(out[3]);
    c1 = 1 - c;
    m1 = 1 - m;
    y1 = 1 - y;
    k1 = 1 - k;
    cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
    rgb->r = clip01(dblToCol(r));
    rgb->g = clip01(dblToCol(g));
    rgb->b = clip01(dblToCol(b));
    return;
  }
#endif
  X *= whiteX;
  Y *= whiteY;
  Z *= whiteZ;
  // convert XYZ to RGB, including gamut mapping and gamma correction
  const double r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
  const double g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
  const double b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
  rgb->r = dblToCol(sqrt(clip01(r * kr)));
  rgb->g = dblToCol(sqrt(clip01(g * kg)));
  rgb->b = dblToCol(sqrt(clip01(b * kb)));
}

void GfxLabColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  GfxRGB rgb;
  GfxColorComp c, m, y, k;

#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
    double in[gfxColorMaxComps];
    unsigned char out[gfxColorMaxComps];
    
    getXYZ(color, &in[0], &in[1], &in[2]);
    transform->doTransform(in,out,1);
    cmyk->c = byteToCol(out[0]);
    cmyk->m = byteToCol(out[1]);
    cmyk->y = byteToCol(out[2]);
    cmyk->k = byteToCol(out[3]);
    return;
  }
#endif
  getRGB(color, &rgb);
  c = clip01(gfxColorComp1 - rgb.r);
  m = clip01(gfxColorComp1 - rgb.g);
  y = clip01(gfxColorComp1 - rgb.b);
  k = c;
  if (m < k) {
    k = m;
  }
  if (y < k) {
    k = y;
  }
  cmyk->c = c - k;
  cmyk->m = m - k;
  cmyk->y = y - k;
  cmyk->k = k;
}

void GfxLabColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  GfxCMYK cmyk;
  clearGfxColor(deviceN);
  getCMYK(color, &cmyk);
  deviceN->c[0] = cmyk.c;
  deviceN->c[1] = cmyk.m;
  deviceN->c[2] = cmyk.y;
  deviceN->c[3] = cmyk.k;
}

void GfxLabColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = 0;
  if (aMin > 0) {
    color->c[1] = dblToCol(aMin);
  } else if (aMax < 0) {
    color->c[1] = dblToCol(aMax);
  } else {
    color->c[1] = 0;
  }
  if (bMin > 0) {
    color->c[2] = dblToCol(bMin);
  } else if (bMax < 0) {
    color->c[2] = dblToCol(bMax);
  } else {
    color->c[2] = 0;
  }
}

void GfxLabColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange,
					int maxImgPixel) const {
  decodeLow[0] = 0;
  decodeRange[0] = 100;
  decodeLow[1] = aMin;
  decodeRange[1] = aMax - aMin;
  decodeLow[2] = bMin;
  decodeRange[2] = bMax - bMin;
}

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

GfxICCBasedColorSpace::GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA) {
  nComps = nCompsA;
  alt = altA;
  rangeMin[0] = rangeMin[1] = rangeMin[2] = rangeMin[3] = 0;
  rangeMax[0] = rangeMax[1] = rangeMax[2] = rangeMax[3] = 1;
#ifdef USE_CMS
  transform = nullptr;
  lineTransform = nullptr;
#endif
}

GfxICCBasedColorSpace::~GfxICCBasedColorSpace() {
  delete alt;
#ifdef USE_CMS
  if (transform != nullptr) {
    if (transform->unref() == 0) delete transform;
  }
  if (lineTransform != nullptr) {
    if (lineTransform->unref() == 0) delete lineTransform;
  }
#endif
}

GfxColorSpace *GfxICCBasedColorSpace::copy() const {
  GfxICCBasedColorSpace *cs;
  int i;

  cs = new GfxICCBasedColorSpace(nComps, alt->copy());
  for (i = 0; i < 4; ++i) {
    cs->rangeMin[i] = rangeMin[i];
    cs->rangeMax[i] = rangeMax[i];
  }
#ifdef USE_CMS
  cs->transform = transform;
  if (transform != nullptr) transform->ref();
  cs->lineTransform = lineTransform;
  if (lineTransform != nullptr) lineTransform->ref();
#endif
  return cs;
}

GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, OutputDev *out, GfxState *state, int recursion) {
  GfxICCBasedColorSpace *cs;
  int nCompsA;
  GfxColorSpace *altA;
  Dict *dict;
  Object obj1, obj2;
  int i;

  if (arr->getLength() < 2) {
    error(errSyntaxError, -1, "Bad ICCBased color space");
    return nullptr;
  }
#ifdef USE_CMS
  const Object &obj1Ref = arr->getNF(1);
  const Ref iccProfileStreamA = obj1Ref.isRef() ? obj1Ref.getRef() : Ref::INVALID();
  // check cache
  if (out && iccProfileStreamA != Ref::INVALID()) {
    if (auto *item = out->getIccColorSpaceCache()->lookup(iccProfileStreamA)) {
      cs = static_cast<GfxICCBasedColorSpace*>(item->copy());
      int transformIntent = cs->getIntent();
      int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
      if (state != nullptr) {
        const char *intent = state->getRenderingIntent();
        if (intent != nullptr) {
          if (strcmp(intent, "AbsoluteColorimetric") == 0) {
            cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
          } else if (strcmp(intent, "Saturation") == 0) {
            cmsIntent = INTENT_SATURATION;
          } else if (strcmp(intent, "Perceptual") == 0) {
            cmsIntent = INTENT_PERCEPTUAL;
          }
        }
      }
      if (transformIntent == cmsIntent) {
        return cs;
      }
      delete cs;
    }
  }
#endif
  obj1 = arr->get(1);
  if (!obj1.isStream()) {
    error(errSyntaxWarning, -1, "Bad ICCBased color space (stream)");
    return nullptr;
  }
  dict = obj1.streamGetDict();
  obj2 = dict->lookup("N");
  if (!obj2.isInt()) {
    error(errSyntaxWarning, -1, "Bad ICCBased color space (N)");
    return nullptr;
  }
  nCompsA = obj2.getInt();
  if (nCompsA > 4) {
    error(errSyntaxError, -1,
	  "ICCBased color space with too many ({0:d} > 4) components",
	  nCompsA);
    nCompsA = 4;
  }
  obj2 = dict->lookup("Alternate");
  if (obj2.isNull() ||
      !(altA = GfxColorSpace::parse(nullptr, &obj2, out, state, recursion + 1))) {
    switch (nCompsA) {
    case 1:
      altA = new GfxDeviceGrayColorSpace();
      break;
    case 3:
      altA = new GfxDeviceRGBColorSpace();
      break;
    case 4:
      altA = new GfxDeviceCMYKColorSpace();
      break;
    default:
      error(errSyntaxWarning, -1, "Bad ICCBased color space - invalid N");
      return nullptr;
    }
  }
  if (altA->getNComps() != nCompsA) {
      error(errSyntaxWarning, -1, "Bad ICCBased color space - N doesn't match alt color space");
      delete altA;
      return nullptr;
  }
  cs = new GfxICCBasedColorSpace(nCompsA, altA);
  obj2 = dict->lookup("Range");
  if (obj2.isArray() && obj2.arrayGetLength() == 2 * nCompsA) {
    for (i = 0; i < nCompsA; ++i) {
      cs->rangeMin[i] = obj2.arrayGet(2*i).getNumWithDefaultValue(0);
      cs->rangeMax[i] = obj2.arrayGet(2*i+1).getNumWithDefaultValue(1);
    }
  }

#ifdef USE_CMS
  obj1 = arr->get(1);
  unsigned char *profBuf;
  Stream *iccStream = obj1.getStream();
  int length = 0;

  profBuf = iccStream->toUnsignedChars(&length, 65536, 65536);
  cmsHPROFILE hp = cmsOpenProfileFromMem(profBuf,length);
  gfree(profBuf);
  if (hp == nullptr) {
    error(errSyntaxWarning, -1, "read ICCBased color space profile error");
  } else {
    cmsHPROFILE dhp = (state != nullptr && state->getDisplayProfile() != nullptr) ? state->getDisplayProfile() : displayProfile;
    if (dhp == nullptr) {
      if (unlikely(RGBProfile == nullptr)) {
        GfxColorSpace::setupColorProfiles();
      }
      dhp = RGBProfile;
    }
    unsigned int cst = getCMSColorSpaceType(cmsGetColorSpace(hp));
    unsigned int dNChannels = getCMSNChannels(cmsGetColorSpace(dhp));
    unsigned int dcst = getCMSColorSpaceType(cmsGetColorSpace(dhp));
    cmsHTRANSFORM transform;

    int cmsIntent = INTENT_RELATIVE_COLORIMETRIC;
    if (state != nullptr) {
      const char *intent = state->getRenderingIntent();
      if (intent != nullptr) {
        if (strcmp(intent, "AbsoluteColorimetric") == 0) {
          cmsIntent = INTENT_ABSOLUTE_COLORIMETRIC;
        } else if (strcmp(intent, "Saturation") == 0) {
          cmsIntent = INTENT_SATURATION;
        } else if (strcmp(intent, "Perceptual") == 0) {
          cmsIntent = INTENT_PERCEPTUAL;
        }
      }
    }
    if ((transform = cmsCreateTransform(hp,
	   COLORSPACE_SH(cst) |CHANNELS_SH(nCompsA) | BYTES_SH(1),
	   dhp,
	   COLORSPACE_SH(dcst) |
	     CHANNELS_SH(dNChannels) | BYTES_SH(1),
	   cmsIntent, LCMS_FLAGS)) == nullptr) {
      error(errSyntaxWarning, -1, "Can't create transform");
      cs->transform = nullptr;
    } else {
      cs->transform = new GfxColorTransform(transform, cmsIntent, cst, dcst);
    }
    if (dcst == PT_RGB || dcst == PT_CMYK) {
       // create line transform only when the display is RGB type color space 
      if ((transform = cmsCreateTransform(hp,
	    CHANNELS_SH(nCompsA) | BYTES_SH(1),dhp,
	    (dcst == PT_RGB) ? TYPE_RGB_8 : TYPE_CMYK_8, cmsIntent, LCMS_FLAGS)) == nullptr) {
	error(errSyntaxWarning, -1, "Can't create transform");
	cs->lineTransform = nullptr;
      } else {
	cs->lineTransform = new GfxColorTransform(transform, cmsIntent, cst, dcst);
      }
    }
    cmsCloseProfile(hp);
  }
  // put this colorSpace into cache
  if (out && iccProfileStreamA != Ref::INVALID()) {
    out->getIccColorSpaceCache()->put(iccProfileStreamA, static_cast<GfxICCBasedColorSpace*>(cs->copy()));
  }
#endif
  return cs;
}

void GfxICCBasedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_GRAY) {
    unsigned char in[gfxColorMaxComps];
    unsigned char out[gfxColorMaxComps];
    
    if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
      in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
      in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
      in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
    } else {
      for (int i = 0;i < nComps;i++) {
	in[i] = colToByte(color->c[i]);
      }
    }
    if (nComps <= 4) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
      if (it != cmsCache.end()) {
        unsigned int value = it->second;
        *gray = byteToCol(value & 0xff);
        return;
      }
    }    
    transform->doTransform(in,out,1);
    *gray = byteToCol(out[0]);
    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      unsigned int value = out[0];
      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
    }
  } else {
    GfxRGB rgb;
    getRGB(color,&rgb);
    *gray = clip01((GfxColorComp)(0.3 * rgb.r +
		   0.59 * rgb.g +
		   0.11 * rgb.b + 0.5));
  }
#else
  alt->getGray(color, gray);
#endif
}

void GfxICCBasedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
    unsigned char in[gfxColorMaxComps];
    unsigned char out[gfxColorMaxComps];
    
    if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
      in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
      in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
      in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
    } else {
      for (int i = 0;i < nComps;i++) {
	in[i] = colToByte(color->c[i]);
      }
    }
    if (nComps <= 4) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
      if (it != cmsCache.end()) {
        unsigned int value = it->second;
        rgb->r = byteToCol(value >> 16);
        rgb->g = byteToCol((value >> 8) & 0xff);
        rgb->b = byteToCol(value & 0xff);
        return;
      }
    }    
    transform->doTransform(in,out,1);
    rgb->r = byteToCol(out[0]);
    rgb->g = byteToCol(out[1]);
    rgb->b = byteToCol(out[2]);
    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      unsigned int value = (out[0] << 16) + (out[1] << 8) + out[2];
      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
    }
  } else if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
    unsigned char in[gfxColorMaxComps];
    unsigned char out[gfxColorMaxComps];
    double c, m, y, k, c1, m1, y1, k1, r, g, b;

    if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
      in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
      in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
      in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
    } else {
      for (int i = 0;i < nComps;i++) {
	in[i] = colToByte(color->c[i]);
      }
    }
    if (nComps <= 4) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
      if (it != cmsCache.end()) {
        unsigned int value = it->second;
        rgb->r = byteToCol(value >> 16);
        rgb->g = byteToCol((value >> 8) & 0xff);
        rgb->b = byteToCol(value & 0xff);
        return;
      }
    }    
    transform->doTransform(in,out,1);
    c = byteToDbl(out[0]);
    m = byteToDbl(out[1]);
    y = byteToDbl(out[2]);
    k = byteToDbl(out[3]);
    c1 = 1 - c;
    m1 = 1 - m;
    y1 = 1 - y;
    k1 = 1 - k;
    cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
    rgb->r = clip01(dblToCol(r));
    rgb->g = clip01(dblToCol(g));
    rgb->b = clip01(dblToCol(b));
    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      unsigned int value = (dblToByte(r) << 16) + (dblToByte(g) << 8) + dblToByte(b);
      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
    }
  } else {
    alt->getRGB(color, rgb);
  }
#else
  alt->getRGB(color, rgb);
#endif
}

void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned int *out,
				       int length) {
#ifdef USE_CMS
  if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
    unsigned char* tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
    lineTransform->doTransform(in, tmp, length);
    for (int i = 0; i < length; ++i) {
        unsigned char *current = tmp + (i * 3);
        out[i] = (current[0] << 16) | (current[1] << 8) | current[2];
    }
    gfree(tmp);
  } else {
    alt->getRGBLine(in, out, length);
  }
#else
  alt->getRGBLine(in, out, length);
#endif
}

void GfxICCBasedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length) {
#ifdef USE_CMS
  if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
    unsigned char* tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
    lineTransform->doTransform(in, tmp, length);
    unsigned char *current = tmp;
    for (int i = 0; i < length; ++i) {
        *out++ = *current++;
        *out++ = *current++;
        *out++ = *current++;
    }
    gfree(tmp);
  } else if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
    unsigned char* tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
    lineTransform->doTransform(in, tmp, length);
    unsigned char *current = tmp;
    double c, m, y, k, c1, m1, y1, k1, r, g, b;
    for (int i = 0; i < length; ++i) {
      c = byteToDbl(*current++);
      m = byteToDbl(*current++);
      y = byteToDbl(*current++);
      k = byteToDbl(*current++);
      c1 = 1 - c;
      m1 = 1 - m;
      y1 = 1 - y;
      k1 = 1 - k;
      cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
      *out++ = dblToByte(r);
      *out++ = dblToByte(g);
      *out++ = dblToByte(b);
    }
    gfree(tmp);
  } else {
    alt->getRGBLine(in, out, length);
  }
#else
  alt->getRGBLine(in, out, length);
#endif
}

void GfxICCBasedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length) {
#ifdef USE_CMS
  if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_RGB) {
    unsigned char* tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
    lineTransform->doTransform(in, tmp, length);
    unsigned char *current = tmp;
    for (int i = 0; i < length; ++i) {
        *out++ = *current++;
        *out++ = *current++;
        *out++ = *current++;
        *out++ = 255;
    }
    gfree(tmp);
  } else {
    alt->getRGBXLine(in, out, length);
  }
#else
  alt->getRGBXLine(in, out, length);
#endif
}

void GfxICCBasedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) {
#ifdef USE_CMS
  if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
    transform->doTransform(in,out,length);
  } else if (lineTransform != nullptr && nComps != 4) {
    GfxColorComp c, m, y, k;
    unsigned char* tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
    getRGBLine(in, tmp, length);
    unsigned char *p = tmp;
    for (int i = 0; i < length; i++) {
      c = byteToCol(255 - *p++);
      m = byteToCol(255 - *p++);
      y = byteToCol(255 - *p++);
      k = c;
      if (m < k) {
        k = m;
      }
      if (y < k) {
        k = y;
      }
      *out++ = colToByte(c - k);
      *out++ = colToByte(m - k);
      *out++ = colToByte(y - k);
      *out++ = colToByte(k);
    }
    gfree(tmp);
  } else {
    alt->getCMYKLine(in, out, length);
  }
#else
  alt->getCMYKLine(in, out, length);
#endif
}

void GfxICCBasedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) {
#ifdef USE_CMS
  if (lineTransform != nullptr && lineTransform->getTransformPixelType() == PT_CMYK) {
    unsigned char* tmp = (unsigned char *)gmallocn(4 * length, sizeof(unsigned char));
    transform->doTransform(in,tmp,length);
    unsigned char *p = tmp;
    for (int i = 0; i < length; i++) {
      for (int j = 0; j < 4; j++)
        *out++ = *p++;
      for (int j = 4; j < SPOT_NCOMPS+4; j++)
        *out++ = 0;
    }
    gfree(tmp);
  } else if (lineTransform != nullptr && nComps != 4) {
    GfxColorComp c, m, y, k;
    unsigned char* tmp = (unsigned char *)gmallocn(3 * length, sizeof(unsigned char));
    getRGBLine(in, tmp, length);
    unsigned char *p = tmp;
    for (int i = 0; i < length; i++) {
      for (int j = 0; j < SPOT_NCOMPS+4; j++) 
        out[j] = 0;
      c = byteToCol(255 - *p++);
      m = byteToCol(255 - *p++);
      y = byteToCol(255 - *p++);
      k = c;
      if (m < k) {
        k = m;
      }
      if (y < k) {
        k = y;
      }
      out[0] = colToByte(c - k);
      out[1] = colToByte(m - k);
      out[2] = colToByte(y - k);
      out[3] = colToByte(k);
      out += (SPOT_NCOMPS+4);
    }
    gfree(tmp);
  } else {
    alt->getDeviceNLine(in, out, length);
  }
#else
  alt->getDeviceNLine(in, out, length);
#endif
}

void GfxICCBasedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
#ifdef USE_CMS
  if (transform != nullptr && transform->getTransformPixelType() == PT_CMYK) {
    unsigned char in[gfxColorMaxComps];
    unsigned char out[gfxColorMaxComps];
    
    if (nComps == 3 && transform->getInputPixelType() == PT_Lab) {
      in[0] = colToByte(dblToCol(colToDbl(color->c[0]) / 100.0));
      in[1] = colToByte(dblToCol((colToDbl(color->c[1]) + 128.0) / 255.0));
      in[2] = colToByte(dblToCol((colToDbl(color->c[2]) + 128.0) / 255.0));
    } else {
      for (int i = 0;i < nComps;i++) {
	in[i] = colToByte(color->c[i]);
      }
    }
    if (nComps <= 4) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      std::map<unsigned int, unsigned int>::iterator it = cmsCache.find(key);
      if (it != cmsCache.end()) {
        unsigned int value = it->second;
        cmyk->c = byteToCol(value >> 24);
        cmyk->m = byteToCol((value >> 16) & 0xff);
        cmyk->y = byteToCol((value >> 8) & 0xff);
        cmyk->k = byteToCol(value & 0xff);
        return;
      }
    }    
    transform->doTransform(in,out,1);
    cmyk->c = byteToCol(out[0]);
    cmyk->m = byteToCol(out[1]);
    cmyk->y = byteToCol(out[2]);
    cmyk->k = byteToCol(out[3]);
    if (nComps <= 4 && cmsCache.size() <= CMSCACHE_LIMIT) {
      unsigned int key = 0;
      for (int j = 0; j < nComps; j++) {
        key = (key << 8) + in[j];
      }
      unsigned int value = (out[0] << 24) + (out[1] << 16) + (out[2] << 8) + out[3];
      cmsCache.insert(std::pair<unsigned int, unsigned int>(key, value));
    }
  } else if (nComps != 4 && transform != nullptr && transform->getTransformPixelType() == PT_RGB) {
    GfxRGB rgb;
    GfxColorComp c, m, y, k;

    getRGB(color,&rgb);
    c = clip01(gfxColorComp1 - rgb.r);
    m = clip01(gfxColorComp1 - rgb.g);
    y = clip01(gfxColorComp1 - rgb.b);
    k = c;
    if (m < k) {
      k = m;
    }
    if (y < k) {
      k = y;
    }
    cmyk->c = c - k;
    cmyk->m = m - k;
    cmyk->y = y - k;
    cmyk->k = k;
  } else {
    alt->getCMYK(color, cmyk);
  }
#else
  alt->getCMYK(color, cmyk);
#endif
}

bool GfxICCBasedColorSpace::useGetRGBLine() const {
#ifdef USE_CMS
  return lineTransform != nullptr || alt->useGetRGBLine();
#else
  return alt->useGetRGBLine();
#endif
}

bool GfxICCBasedColorSpace::useGetCMYKLine() const {
#ifdef USE_CMS
  return lineTransform != nullptr || alt->useGetCMYKLine();
#else
  return alt->useGetCMYKLine();
#endif
}

bool GfxICCBasedColorSpace::useGetDeviceNLine() const {
#ifdef USE_CMS
  return lineTransform != nullptr || alt->useGetDeviceNLine();
#else
  return alt->useGetDeviceNLine();
#endif
}

void GfxICCBasedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  GfxCMYK cmyk;
  clearGfxColor(deviceN);
  getCMYK(color, &cmyk);
  deviceN->c[0] = cmyk.c;
  deviceN->c[1] = cmyk.m;
  deviceN->c[2] = cmyk.y;
  deviceN->c[3] = cmyk.k;
}

void GfxICCBasedColorSpace::getDefaultColor(GfxColor *color) const {
  int i;

  for (i = 0; i < nComps; ++i) {
    if (rangeMin[i] > 0) {
      color->c[i] = dblToCol(rangeMin[i]);
    } else if (rangeMax[i] < 0) {
      color->c[i] = dblToCol(rangeMax[i]);
    } else {
      color->c[i] = 0;
    }
  }
}

void GfxICCBasedColorSpace::getDefaultRanges(double *decodeLow,
					     double *decodeRange,
					     int maxImgPixel) const {
  alt->getDefaultRanges(decodeLow, decodeRange, maxImgPixel);

#if 0
  // this is nominally correct, but some PDF files don't set the
  // correct ranges in the ICCBased dict
  int i;

  for (i = 0; i < nComps; ++i) {
    decodeLow[i] = rangeMin[i];
    decodeRange[i] = rangeMax[i] - rangeMin[i];
  }
#endif
}

//------------------------------------------------------------------------
// GfxIndexedColorSpace
//------------------------------------------------------------------------

GfxIndexedColorSpace::GfxIndexedColorSpace(GfxColorSpace *baseA,
					   int indexHighA) {
  base = baseA;
  indexHigh = indexHighA;
  lookup = (unsigned char *)gmallocn((indexHigh + 1) * base->getNComps(),
			      sizeof(unsigned char));
  overprintMask = base->getOverprintMask();
}

GfxIndexedColorSpace::~GfxIndexedColorSpace() {
  delete base;
  gfree(lookup);
}

GfxColorSpace *GfxIndexedColorSpace::copy() const {
  GfxIndexedColorSpace *cs;

  cs = new GfxIndexedColorSpace(base->copy(), indexHigh);
  memcpy(cs->lookup, lookup,
	 (indexHigh + 1) * base->getNComps() * sizeof(unsigned char));
  return cs;
}

GfxColorSpace *GfxIndexedColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) {
  GfxColorSpace *baseA;
  int indexHighA;
  Object obj1;
  const char *s;
  int i, j;

  if (arr->getLength() != 4) {
    error(errSyntaxWarning, -1, "Bad Indexed color space");
    return nullptr;
  }
  obj1 = arr->get(1);
  if (!(baseA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) {
    error(errSyntaxWarning, -1, "Bad Indexed color space (base color space)");
    return nullptr;
  }
  obj1 = arr->get(2);
  if (!obj1.isInt()) {
    error(errSyntaxWarning, -1, "Bad Indexed color space (hival)");
    delete baseA;
    return nullptr;
  }
  indexHighA = obj1.getInt();
  if (indexHighA < 0 || indexHighA > 255) {
    // the PDF spec requires indexHigh to be in [0,255] -- allowing
    // values larger than 255 creates a security hole: if nComps *
    // indexHigh is greater than 2^31, the loop below may overwrite
    // past the end of the array
    int previousValue = indexHighA;
    if (indexHighA < 0) indexHighA = 0;
    else indexHighA = 255;
    error(errSyntaxWarning, -1, "Bad Indexed color space (invalid indexHigh value, was {0:d} using {1:d} to try to recover)", previousValue, indexHighA);
  }
  GfxIndexedColorSpace *cs = new GfxIndexedColorSpace(baseA, indexHighA);
  obj1 = arr->get(3);
  const int n = baseA->getNComps();
  if (obj1.isStream()) {
    obj1.streamReset();
    for (i = 0; i <= indexHighA; ++i) {
      const int readChars = obj1.streamGetChars(n, &cs->lookup[i*n]);
      for (j = readChars; j < n; ++j) {
        error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table stream too short) padding with zeroes");
        cs->lookup[i*n + j] = 0;
      }
    }
    obj1.streamClose();
  } else if (obj1.isString()) {
    if (obj1.getString()->getLength() < (indexHighA + 1) * n) {
      error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table string too short)");
      goto err3;
    }
    s = obj1.getString()->c_str();
    for (i = 0; i <= indexHighA; ++i) {
      for (j = 0; j < n; ++j) {
	cs->lookup[i*n + j] = (unsigned char)*s++;
      }
    }
  } else {
    error(errSyntaxWarning, -1, "Bad Indexed color space (lookup table)");
    goto err3;
  }
  return cs;

 err3:
  delete cs;
  return nullptr;
}

GfxColor *GfxIndexedColorSpace::mapColorToBase(const GfxColor *color,
					       GfxColor *baseColor) const {
  unsigned char *p;
  double low[gfxColorMaxComps], range[gfxColorMaxComps];
  int n, i;

  n = base->getNComps();
  base->getDefaultRanges(low, range, indexHigh);
  const int idx = (int)(colToDbl(color->c[0]) + 0.5) * n;
  if (likely((idx + n - 1 < (indexHigh + 1) * base->getNComps()) && idx >= 0)) {
    p = &lookup[idx];
    for (i = 0; i < n; ++i) {
      baseColor->c[i] = dblToCol(low[i] + (p[i] / 255.0) * range[i]);
    }
  } else {
    for (i = 0; i < n; ++i) {
      baseColor->c[i] = 0;
    }
  }
  return baseColor;
}

void GfxIndexedColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  GfxColor color2;

  base->getGray(mapColorToBase(color, &color2), gray);
}

void GfxIndexedColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  GfxColor color2;

  base->getRGB(mapColorToBase(color, &color2), rgb);
}

void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned int *out, int length) {
  unsigned char *line;
  int i, j, n;

  n = base->getNComps();
  line = (unsigned char *) gmallocn (length, n);
  for (i = 0; i < length; i++)
    for (j = 0; j < n; j++)
      line[i * n + j] = lookup[in[i] * n + j];

  base->getRGBLine(line, out, length);

  gfree (line);
}

void GfxIndexedColorSpace::getRGBLine(unsigned char *in, unsigned char *out, int length)
{
  unsigned char *line;
  int i, j, n;

  n = base->getNComps();
  line = (unsigned char *) gmallocn (length, n);
  for (i = 0; i < length; i++)
    for (j = 0; j < n; j++)
      line[i * n + j] = lookup[in[i] * n + j];

  base->getRGBLine(line, out, length);

  gfree (line);
}

void GfxIndexedColorSpace::getRGBXLine(unsigned char *in, unsigned char *out, int length)
{
  unsigned char *line;
  int i, j, n;

  n = base->getNComps();
  line = (unsigned char *) gmallocn (length, n);
  for (i = 0; i < length; i++)
    for (j = 0; j < n; j++)
      line[i * n + j] = lookup[in[i] * n + j];

  base->getRGBXLine(line, out, length);

  gfree (line);
}

void GfxIndexedColorSpace::getCMYKLine(unsigned char *in, unsigned char *out, int length) {
  unsigned char *line;
  int i, j, n;

  n = base->getNComps();
  line = (unsigned char *) gmallocn (length, n);
  for (i = 0; i < length; i++)
    for (j = 0; j < n; j++)
      line[i * n + j] = lookup[in[i] * n + j];

  base->getCMYKLine(line, out, length);

  gfree (line);
}

void GfxIndexedColorSpace::getDeviceNLine(unsigned char *in, unsigned char *out, int length) {
  unsigned char *line;
  int i, j, n;

  n = base->getNComps();
  line = (unsigned char *) gmallocn (length, n);
  for (i = 0; i < length; i++)
    for (j = 0; j < n; j++)
      line[i * n + j] = lookup[in[i] * n + j];

  base->getDeviceNLine(line, out, length);

  gfree (line);
}

void GfxIndexedColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  GfxColor color2;

  base->getCMYK(mapColorToBase(color, &color2), cmyk);
}

void GfxIndexedColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  GfxColor color2;

  base->getDeviceN(mapColorToBase(color, &color2), deviceN);
}

void GfxIndexedColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = 0;
}

void GfxIndexedColorSpace::getDefaultRanges(double *decodeLow,
					    double *decodeRange,
					    int maxImgPixel) const {
  decodeLow[0] = 0;
  decodeRange[0] = maxImgPixel;
}

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

GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA,
						 GfxColorSpace *altA,
						 Function *funcA) {
  name = nameA;
  alt = altA;
  func = funcA;
  nonMarking = !name->cmp("None");
  if (!name->cmp("Cyan")) {
    overprintMask = 0x01;
  } else if (!name->cmp("Magenta")) {
    overprintMask = 0x02;
  } else if (!name->cmp("Yellow")) {
    overprintMask = 0x04;
  } else if (!name->cmp("Black")) {
    overprintMask = 0x08;
  } else if (!name->cmp("All")) {
    overprintMask = 0xffffffff;
  }
}

GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA,
						 GfxColorSpace *altA,
						 Function *funcA,
						 bool nonMarkingA,
						 unsigned int overprintMaskA,
						 int *mappingA) {
  name = nameA;
  alt = altA;
  func = funcA;
  nonMarking = nonMarkingA;
  overprintMask = overprintMaskA;
  mapping = mappingA;
}

GfxSeparationColorSpace::~GfxSeparationColorSpace() {
  delete name;
  delete alt;
  delete func;
  if (mapping != nullptr)
    gfree(mapping);
}

GfxColorSpace *GfxSeparationColorSpace::copy() const {
  int *mappingA = nullptr;
  if (mapping != nullptr) {
    mappingA = (int *) gmalloc(sizeof(int));
    *mappingA = *mapping;
  }
  return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(),
				     nonMarking, overprintMask, mappingA);
}

//~ handle the 'All' and 'None' colorants
GfxColorSpace *GfxSeparationColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) {
  GfxSeparationColorSpace *cs;
  GooString *nameA;
  GfxColorSpace *altA;
  Function *funcA;
  Object obj1;

  if (arr->getLength() != 4) {
    error(errSyntaxWarning, -1, "Bad Separation color space");
    goto err1;
  }
  obj1 = arr->get(1);
  if (!obj1.isName()) {
    error(errSyntaxWarning, -1, "Bad Separation color space (name)");
    goto err1;
  }
  nameA = new GooString(obj1.getName());
  obj1 = arr->get(2);
  if (!(altA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) {
    error(errSyntaxWarning, -1, "Bad Separation color space (alternate color space)");
    goto err3;
  }
  obj1 = arr->get(3);
  if (!(funcA = Function::parse(&obj1))) {
    goto err4;
  }
  if (funcA->getInputSize() != 1) {
    error(errSyntaxWarning, -1, "Bad SeparationColorSpace function");
    goto err5;
  }
  cs = new GfxSeparationColorSpace(nameA, altA, funcA);
  return cs;

 err5:
  delete funcA;
 err4:
  delete altA;
 err3:
  delete nameA;
 err1:
  return nullptr;
}

void GfxSeparationColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  double x;
  double c[gfxColorMaxComps];
  GfxColor color2;
  int i;

  if (alt->getMode() == csDeviceGray && name->cmp("Black") == 0) {
    *gray = clip01(gfxColorComp1 - color->c[0]);
  } else {
    x = colToDbl(color->c[0]);
    func->transform(&x, c);
    for (i = 0; i < alt->getNComps(); ++i) {
      color2.c[i] = dblToCol(c[i]);
    }
    alt->getGray(&color2, gray);
  }
}

void GfxSeparationColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  double x;
  double c[gfxColorMaxComps];
  GfxColor color2;
  int i;

  if (alt->getMode() == csDeviceGray && name->cmp("Black") == 0) {
    rgb->r = clip01(gfxColorComp1 - color->c[0]);
    rgb->g = clip01(gfxColorComp1 - color->c[0]);
    rgb->b = clip01(gfxColorComp1 - color->c[0]);
  } else {
    x = colToDbl(color->c[0]);
    func->transform(&x, c);
    const int altNComps = alt->getNComps();
    for (i = 0; i < altNComps; ++i) {
      color2.c[i] = dblToCol(c[i]);
    }
    if (unlikely(altNComps > func->getOutputSize())) {
      for (i = func->getOutputSize(); i < altNComps; ++i) {
	color2.c[i] = 0;
      }
    }
    alt->getRGB(&color2, rgb);
  }
}

void GfxSeparationColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  double x;
  double c[gfxColorMaxComps];
  GfxColor color2;
  int i;

  if (name->cmp("Black") == 0) {
    cmyk->c = 0;
    cmyk->m = 0;
    cmyk->y = 0;
    cmyk->k = color->c[0];
  } else if (name->cmp("Cyan") == 0) {
    cmyk->c = color->c[0];
    cmyk->m = 0;
    cmyk->y = 0;
    cmyk->k = 0;
  } else if (name->cmp("Magenta") == 0) {
    cmyk->c = 0;
    cmyk->m = color->c[0];
    cmyk->y = 0;
    cmyk->k = 0;
  } else if (name->cmp("Yellow") == 0) {
    cmyk->c = 0;
    cmyk->m = 0;
    cmyk->y = color->c[0];
    cmyk->k = 0;
  } else {
    x = colToDbl(color->c[0]);
    func->transform(&x, c);
    for (i = 0; i < alt->getNComps(); ++i) {
      color2.c[i] = dblToCol(c[i]);
    }
    alt->getCMYK(&color2, cmyk);
  }
}

void GfxSeparationColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  clearGfxColor(deviceN);
  if (mapping == nullptr || mapping[0] == -1) {
    GfxCMYK cmyk;

    getCMYK(color, &cmyk);
    deviceN->c[0] = cmyk.c;
    deviceN->c[1] = cmyk.m;
    deviceN->c[2] = cmyk.y;
    deviceN->c[3] = cmyk.k;
  } else {
    deviceN->c[mapping[0]] = color->c[0];
  }
}

void GfxSeparationColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0] = gfxColorComp1;
}

void GfxSeparationColorSpace::createMapping(std::vector<GfxSeparationColorSpace*> *separationList, int maxSepComps) {
  if (nonMarking)
    return;
  mapping = (int *)gmalloc(sizeof(int));
  switch (overprintMask) {
    case 0x01:
      *mapping = 0;
      break;
    case 0x02:
      *mapping = 1;
      break;
    case 0x04:
      *mapping = 2;
      break;
    case 0x08:
      *mapping = 3;
      break;
    default:
      unsigned int newOverprintMask = 0x10;
      for (std::size_t i = 0; i < separationList->size(); i++) {
        GfxSeparationColorSpace *sepCS = (*separationList)[i];
        if (!sepCS->getName()->cmp(name)) {
          if (sepCS->getFunc()->hasDifferentResultSet(func)) {
            error(errSyntaxWarning, -1,
              "Different functions found for '{0:t}', convert immediately", name);
            gfree(mapping);
            mapping = nullptr;
            return;
          }
          *mapping = i+4;
          overprintMask = newOverprintMask;
          return;
        }
        newOverprintMask <<=1;
      }
      if ((int)separationList->size() == maxSepComps) {
        error(errSyntaxWarning, -1,
	        "Too many ({0:d}) spots, convert '{1:t}' immediately", maxSepComps, name);
        gfree(mapping);
        mapping = nullptr;
        return;
      }
      *mapping = separationList->size() + 4;
      separationList->push_back((GfxSeparationColorSpace*)copy());
      overprintMask = newOverprintMask;
      break;
  }
}

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

GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
					   std::vector<std::string> &&namesA,
					   GfxColorSpace *altA,
					   Function *funcA,
					   std::vector<GfxSeparationColorSpace*> *sepsCSA)
 : nComps(nCompsA)
 , names(std::move(namesA))
{
  alt = altA;
  func = funcA;
  sepsCS = sepsCSA;
  nonMarking = true;
  overprintMask = 0;
  mapping = nullptr;
  for (int i = 0; i < nComps; ++i) {
    if (names[i] != "None") {
      nonMarking = false;
    }
    if (names[i] == "Cyan") {
      overprintMask |= 0x01;
    } else if (names[i] == "Magenta") {
      overprintMask |= 0x02;
    } else if (names[i] == "Yellow") {
      overprintMask |= 0x04;
    } else if (names[i] == "Black") {
      overprintMask |= 0x08;
    } else if (names[i] == "All") {
      overprintMask = 0xffffffff;
    } else {
      overprintMask = 0x0f;
    }
  }
}

GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
					   const std::vector<std::string> &namesA,
					   GfxColorSpace *altA,
					   Function *funcA,
					   std::vector<GfxSeparationColorSpace*> *sepsCSA,
					   int *mappingA,
					   bool nonMarkingA,
					   unsigned int overprintMaskA)
 : nComps(nCompsA)
 , names(namesA)
{
  alt = altA;
  func = funcA;
  sepsCS = sepsCSA;
  mapping = mappingA;
  nonMarking = nonMarkingA;
  overprintMask = overprintMaskA;
}

GfxDeviceNColorSpace::~GfxDeviceNColorSpace() {
  delete alt;
  delete func;
  for (auto entry : *sepsCS) {
    delete entry;
  }
  delete sepsCS;
  if (mapping != nullptr)
    gfree(mapping);
}

GfxColorSpace *GfxDeviceNColorSpace::copy() const {
  int *mappingA = nullptr;

  auto sepsCSA = new std::vector<GfxSeparationColorSpace*>();
  sepsCSA->reserve(sepsCS->size());
  for (const GfxSeparationColorSpace *scs : *sepsCS) {
    if (likely(scs != nullptr)) {
      sepsCSA->push_back((GfxSeparationColorSpace*)scs->copy());
    }
  }
  if (mapping != nullptr) {
    mappingA = (int *)gmalloc(sizeof(int) * nComps);
    for (int i = 0; i < nComps; i++)
      mappingA[i] = mapping[i];
  }
  return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(),
				  sepsCSA, mappingA, nonMarking, overprintMask);
}

//~ handle the 'None' colorant
GfxColorSpace *GfxDeviceNColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) {
  int nCompsA;
  std::vector<std::string> namesA;
  GfxColorSpace *altA;
  Function *funcA;
  Object obj1;
  auto separationList = new std::vector<GfxSeparationColorSpace*>();

  if (arr->getLength() != 4 && arr->getLength() != 5) {
    error(errSyntaxWarning, -1, "Bad DeviceN color space");
    goto err1;
  }
  obj1 = arr->get(1);
  if (!obj1.isArray()) {
    error(errSyntaxWarning, -1, "Bad DeviceN color space (names)");
    goto err1;
  }
  nCompsA = obj1.arrayGetLength();
  if (nCompsA > gfxColorMaxComps) {
    error(errSyntaxWarning, -1, "DeviceN color space with too many ({0:d} > {1:d}) components",
	  nCompsA, gfxColorMaxComps);
    nCompsA = gfxColorMaxComps;
  }
  for (int i = 0; i < nCompsA; ++i) {
    Object obj2 = obj1.arrayGet(i);
    if (!obj2.isName()) {
      error(errSyntaxWarning, -1, "Bad DeviceN color space (names)");
      nCompsA = i;
      goto err1;
    }
    namesA.emplace_back(obj2.getName());
  }
  obj1 = arr->get(2);
  if (!(altA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) {
    error(errSyntaxWarning, -1, "Bad DeviceN color space (alternate color space)");
    goto err1;
  }
  obj1 = arr->get(3);
  if (!(funcA = Function::parse(&obj1))) {
    goto err4;
  }
  if (arr->getLength() == 5) {
    obj1 = arr->get(4);
    if (!obj1.isDict()) {
      error(errSyntaxWarning, -1, "Bad DeviceN color space (attributes)");
      goto err5;
    }
    Dict *attribs = obj1.getDict();
    Object obj2 = attribs->lookup("Colorants");
    if (obj2.isDict()) {
      Dict *colorants = obj2.getDict();
      for (int i = 0; i < colorants->getLength(); i++) {
        Object obj3 = colorants->getVal(i);
        if (obj3.isArray()) {
	  GfxSeparationColorSpace *cs = (GfxSeparationColorSpace*)GfxSeparationColorSpace::parse(res, obj3.getArray(), out, state, recursion);
	  if (cs) {
	    separationList->push_back(cs);
	  }
        } else {
          error(errSyntaxWarning, -1, "Bad DeviceN color space (colorant value entry is not an Array)");
          goto err5;
        }
      }
    }
  }
  return new GfxDeviceNColorSpace(nCompsA, std::move(namesA), altA, funcA, separationList);

 err5:
  delete funcA;
 err4:
  delete altA;
 err1:
  delete separationList;
  return nullptr;
}

void GfxDeviceNColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  double x[gfxColorMaxComps], c[gfxColorMaxComps];
  GfxColor color2;
  int i;

  for (i = 0; i < nComps; ++i) {
    x[i] = colToDbl(color->c[i]);
  }
  func->transform(x, c);
  for (i = 0; i < alt->getNComps(); ++i) {
    color2.c[i] = dblToCol(c[i]);
  }
  alt->getGray(&color2, gray);
}

void GfxDeviceNColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  double x[gfxColorMaxComps], c[gfxColorMaxComps];
  GfxColor color2;
  int i;

  for (i = 0; i < nComps; ++i) {
    x[i] = colToDbl(color->c[i]);
  }
  func->transform(x, c);
  for (i = 0; i < alt->getNComps(); ++i) {
    color2.c[i] = dblToCol(c[i]);
  }
  alt->getRGB(&color2, rgb);
}

void GfxDeviceNColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  double x[gfxColorMaxComps], c[gfxColorMaxComps];
  GfxColor color2;
  int i;

  for (i = 0; i < nComps; ++i) {
    x[i] = colToDbl(color->c[i]);
  }
  func->transform(x, c);
  for (i = 0; i < alt->getNComps(); ++i) {
    color2.c[i] = dblToCol(c[i]);
  }
  alt->getCMYK(&color2, cmyk);
}

void GfxDeviceNColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  clearGfxColor(deviceN);
  if (mapping == nullptr) {
    GfxCMYK cmyk;

    getCMYK(color, &cmyk);
    deviceN->c[0] = cmyk.c;
    deviceN->c[1] = cmyk.m;
    deviceN->c[2] = cmyk.y;
    deviceN->c[3] = cmyk.k;
  } else {
    for (int j = 0; j < nComps; j++)
      if (mapping[j] != -1)
        deviceN->c[mapping[j]] = color->c[j];
  }
}

void GfxDeviceNColorSpace::getDefaultColor(GfxColor *color) const {
  int i;

  for (i = 0; i < nComps; ++i) {
    color->c[i] = gfxColorComp1;
  }
}

void GfxDeviceNColorSpace::createMapping(std::vector<GfxSeparationColorSpace*> *separationList, int maxSepComps) {
  if (nonMarking)               // None
    return;
  mapping = (int *)gmalloc(sizeof(int) * nComps);
  unsigned int newOverprintMask = 0;
  for (int i = 0; i < nComps; i++) {
    if (names[i] == "None") {
      mapping[i] = -1;
    } else if (names[i] == "Cyan") {
      newOverprintMask |= 0x01;
      mapping[i] = 0;
    } else if (names[i] == "Magenta") {
      newOverprintMask |= 0x02;
      mapping[i] = 1;
    } else if (names[i] == "Yellow") {
      newOverprintMask |= 0x04;
      mapping[i] = 2;
    } else if (names[i] == "Black") {
      newOverprintMask |= 0x08;
      mapping[i] = 3;
    } else {
      unsigned int startOverprintMask = 0x10;
      bool found = false;
      const Function *sepFunc = nullptr;
      if (nComps == 1)
        sepFunc = func;
      else {
        for (const GfxSeparationColorSpace *sepCS : *sepsCS) {
          if (!sepCS->getName()->cmp(names[i])) {
            sepFunc = sepCS->getFunc();
            break;
          }
        }
      }
      for (std::size_t j = 0; j < separationList->size(); j++) {
        GfxSeparationColorSpace *sepCS = (*separationList)[j];
        if (!sepCS->getName()->cmp(names[i])) {
          if (sepFunc != nullptr && sepCS->getFunc()->hasDifferentResultSet(sepFunc)) {
            error(errSyntaxWarning, -1,
              "Different functions found for '{0:s}', convert immediately", names[i].c_str());
            gfree(mapping);
            mapping = nullptr;
            overprintMask = 0xffffffff;
            return;
          }
          mapping[i] = j+4;
          newOverprintMask |= startOverprintMask;
          found = true;
          break;
        }
        startOverprintMask <<=1;
      }
      if (!found) {
        if ((int)separationList->size() == maxSepComps) {
          error(errSyntaxWarning, -1,
            "Too many ({0:d}) spots, convert '{1:s}' immediately", maxSepComps, names[i].c_str());
          gfree(mapping);
          mapping = nullptr;
          overprintMask = 0xffffffff;
          return;
        }
        mapping[i] = separationList->size() + 4;
        newOverprintMask |= startOverprintMask;
        if (nComps == 1)
	  separationList->push_back(new GfxSeparationColorSpace(new GooString(names[i]),alt->copy(), func->copy()));
        else {
          for (const GfxSeparationColorSpace *sepCS : *sepsCS) {
            if (!sepCS->getName()->cmp(names[i])) {
              found = true;
	      separationList->push_back((GfxSeparationColorSpace*)sepCS->copy());
              break;
            }
          }
          if(!found) {
            error(errSyntaxWarning, -1, "DeviceN has no suitable colorant");
            gfree(mapping);
            mapping = nullptr;
            overprintMask = 0xffffffff;
            return;
          }
        }
      }
    }
  }
  overprintMask = newOverprintMask;
}

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

GfxPatternColorSpace::GfxPatternColorSpace(GfxColorSpace *underA) {
  under = underA;
}

GfxPatternColorSpace::~GfxPatternColorSpace() {
  if (under) {
    delete under;
  }
}

GfxColorSpace *GfxPatternColorSpace::copy() const {
  return new GfxPatternColorSpace(under ? under->copy() :
				          nullptr);
}

GfxColorSpace *GfxPatternColorSpace::parse(GfxResources *res, Array *arr, OutputDev *out, GfxState *state, int recursion) {
  GfxPatternColorSpace *cs;
  GfxColorSpace *underA;
  Object obj1;

  if (arr->getLength() != 1 && arr->getLength() != 2) {
    error(errSyntaxWarning, -1, "Bad Pattern color space");
    return nullptr;
  }
  underA = nullptr;
  if (arr->getLength() == 2) {
    obj1 = arr->get(1);
    if (!(underA = GfxColorSpace::parse(res, &obj1, out, state, recursion + 1))) {
      error(errSyntaxWarning, -1, "Bad Pattern color space (underlying color space)");
      return nullptr;
    }
  }
  cs = new GfxPatternColorSpace(underA);
  return cs;
}

void GfxPatternColorSpace::getGray(const GfxColor *color, GfxGray *gray) const {
  *gray = 0;
}

void GfxPatternColorSpace::getRGB(const GfxColor *color, GfxRGB *rgb) const {
  rgb->r = rgb->g = rgb->b = 0;
}

void GfxPatternColorSpace::getCMYK(const GfxColor *color, GfxCMYK *cmyk) const {
  cmyk->c = cmyk->m = cmyk->y = 0;
  cmyk->k = 1;
}

void GfxPatternColorSpace::getDeviceN(const GfxColor *color, GfxColor *deviceN) const {
  clearGfxColor(deviceN);
  deviceN->c[3] = 1;
}

void GfxPatternColorSpace::getDefaultColor(GfxColor *color) const {
  color->c[0]=0;
}

//------------------------------------------------------------------------
// Pattern
//------------------------------------------------------------------------

GfxPattern::GfxPattern(int typeA, int patternRefNumA)
 : type(typeA)
 , patternRefNum(patternRefNumA)
{

}

GfxPattern::~GfxPattern() {
}

GfxPattern *GfxPattern::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state, int patternRefNum) {
  GfxPattern *pattern;
  Object obj1;

  if (obj->isDict()) {
    obj1 = obj->dictLookup("PatternType");
  } else if (obj->isStream()) {
    obj1 = obj->streamGetDict()->lookup("PatternType");
  } else {
    return nullptr;
  }
  pattern = nullptr;
  if (obj1.isInt() && obj1.getInt() == 1) {
    pattern = GfxTilingPattern::parse(obj, patternRefNum);
  } else if (obj1.isInt() && obj1.getInt() == 2) {
    pattern = GfxShadingPattern::parse(res, obj, out, state, patternRefNum);
  }
  return pattern;
}

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

GfxTilingPattern *GfxTilingPattern::parse(Object *patObj, int patternRefNum) {
  Dict *dict;
  int paintTypeA, tilingTypeA;
  double bboxA[4], matrixA[6];
  double xStepA, yStepA;
  Object resDictA;
  Object obj1;
  int i;

  if (!patObj->isStream()) {
    return nullptr;
  }
  dict = patObj->streamGetDict();

  obj1 = dict->lookup("PaintType");
  if (obj1.isInt()) {
    paintTypeA = obj1.getInt();
  } else {
    paintTypeA = 1;
    error(errSyntaxWarning, -1, "Invalid or missing PaintType in pattern");
  }
  obj1 = dict->lookup("TilingType");
  if (obj1.isInt()) {
    tilingTypeA = obj1.getInt();
  } else {
    tilingTypeA = 1;
    error(errSyntaxWarning, -1, "Invalid or missing TilingType in pattern");
  }
  bboxA[0] = bboxA[1] = 0;
  bboxA[2] = bboxA[3] = 1;
  obj1 = dict->lookup("BBox");
  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
    for (i = 0; i < 4; ++i) {
      Object obj2 = obj1.arrayGet(i);
      if (obj2.isNum()) {
	bboxA[i] = obj2.getNum();
      }
    }
  } else {
    error(errSyntaxWarning, -1, "Invalid or missing BBox in pattern");
  }
  obj1 = dict->lookup("XStep");
  if (obj1.isNum()) {
    xStepA = obj1.getNum();
  } else {
    xStepA = 1;
    error(errSyntaxWarning, -1, "Invalid or missing XStep in pattern");
  }
  obj1 = dict->lookup("YStep");
  if (obj1.isNum()) {
    yStepA = obj1.getNum();
  } else {
    yStepA = 1;
    error(errSyntaxWarning, -1, "Invalid or missing YStep in pattern");
  }
  resDictA = dict->lookup("Resources");
  if (!resDictA.isDict()) {
    error(errSyntaxWarning, -1, "Invalid or missing Resources in pattern");
  }
  matrixA[0] = 1; matrixA[1] = 0;
  matrixA[2] = 0; matrixA[3] = 1;
  matrixA[4] = 0; matrixA[5] = 0;
  obj1 = dict->lookup("Matrix");
  if (obj1.isArray() && obj1.arrayGetLength() == 6) {
    for (i = 0; i < 6; ++i) {
      Object obj2 = obj1.arrayGet(i);
      if (obj2.isNum()) {
	matrixA[i] = obj2.getNum();
      }
    }
  }

  return new GfxTilingPattern(paintTypeA, tilingTypeA, bboxA, xStepA, yStepA,
			     &resDictA, matrixA, patObj, patternRefNum);
}

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

  paintType = paintTypeA;
  tilingType = tilingTypeA;
  for (i = 0; i < 4; ++i) {
    bbox[i] = bboxA[i];
  }
  xStep = xStepA;
  yStep = yStepA;
  resDict = resDictA->copy();
  for (i = 0; i < 6; ++i) {
    matrix[i] = matrixA[i];
  }
  contentStream = contentStreamA->copy();
}

GfxTilingPattern::~GfxTilingPattern() {
}

GfxPattern *GfxTilingPattern::copy() const {
  return new GfxTilingPattern(paintType, tilingType, bbox, xStep, yStep,
			      &resDict, matrix, &contentStream, getPatternRefNum());
}

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

GfxShadingPattern *GfxShadingPattern::parse(GfxResources *res, Object *patObj, OutputDev *out, GfxState *state, int patternRefNum) {
  Dict *dict;
  GfxShading *shadingA;
  double matrixA[6];
  Object obj1;
  int i;

  if (!patObj->isDict()) {
    return nullptr;
  }
  dict = patObj->getDict();

  obj1 = dict->lookup("Shading");
  shadingA = GfxShading::parse(res, &obj1, out, state);
  if (!shadingA) {
    return nullptr;
  }

  matrixA[0] = 1; matrixA[1] = 0;
  matrixA[2] = 0; matrixA[3] = 1;
  matrixA[4] = 0; matrixA[5] = 0;
  obj1 = dict->lookup("Matrix");
  if (obj1.isArray() && obj1.arrayGetLength() == 6) {
    for (i = 0; i < 6; ++i) {
      Object obj2 = obj1.arrayGet(i);
      if (obj2.isNum()) {
	matrixA[i] = obj2.getNum();
      }
    }
  }

  return new GfxShadingPattern(shadingA, matrixA, patternRefNum);
}

GfxShadingPattern::GfxShadingPattern(GfxShading *shadingA, const double *matrixA, int patternRefNumA):
  GfxPattern(2, patternRefNumA)
{
  int i;

  shading = shadingA;
  for (i = 0; i < 6; ++i) {
    matrix[i] = matrixA[i];
  }
}

GfxShadingPattern::~GfxShadingPattern() {
  delete shading;
}

GfxPattern *GfxShadingPattern::copy() const {
  return new GfxShadingPattern(shading->copy(), matrix, getPatternRefNum());
}

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

GfxShading::GfxShading(int typeA) {
  type = typeA;
  colorSpace = nullptr;
}

GfxShading::GfxShading(const GfxShading *shading) {
  int i;

  type = shading->type;
  colorSpace = shading->colorSpace->copy();
  for (i = 0; i < gfxColorMaxComps; ++i) {
    background.c[i] = shading->background.c[i];
  }
  hasBackground = shading->hasBackground;
  bbox_xMin = shading->bbox_xMin;
  bbox_yMin = shading->bbox_yMin;
  bbox_xMax = shading->bbox_xMax;
  bbox_yMax = shading->bbox_yMax;
  hasBBox = shading->hasBBox;
}

GfxShading::~GfxShading() {
  if (colorSpace) {
    delete colorSpace;
  }
}

GfxShading *GfxShading::parse(GfxResources *res, Object *obj, OutputDev *out, GfxState *state) {
  GfxShading *shading;
  Dict *dict;
  int typeA;
  Object obj1;

  if (obj->isDict()) {
    dict = obj->getDict();
  } else if (obj->isStream()) {
    dict = obj->streamGetDict();
  } else {
    return nullptr;
  }

  obj1 = dict->lookup("ShadingType");
  if (!obj1.isInt()) {
    error(errSyntaxWarning, -1, "Invalid ShadingType in shading dictionary");
    return nullptr;
  }
  typeA = obj1.getInt();

  switch (typeA) {
  case 1:
    shading = GfxFunctionShading::parse(res, dict, out, state);
    break;
  case 2:
    shading = GfxAxialShading::parse(res, dict, out, state);
    break;
  case 3:
    shading = GfxRadialShading::parse(res, dict, out, state);
    break;
  case 4:
    if (obj->isStream()) {
      shading = GfxGouraudTriangleShading::parse(res, 4, dict, obj->getStream(), out, state);
    } else {
      error(errSyntaxWarning, -1, "Invalid Type 4 shading object");
      goto err1;
    }
    break;
  case 5:
    if (obj->isStream()) {
      shading = GfxGouraudTriangleShading::parse(res, 5, dict, obj->getStream(), out, state);
    } else {
      error(errSyntaxWarning, -1, "Invalid Type 5 shading object");
      goto err1;
    }
    break;
  case 6:
    if (obj->isStream()) {
      shading = GfxPatchMeshShading::parse(res, 6, dict, obj->getStream(), out, state);
    } else {
      error(errSyntaxWarning, -1, "Invalid Type 6 shading object");
      goto err1;
    }
    break;
  case 7:
    if (obj->isStream()) {
      shading = GfxPatchMeshShading::parse(res, 7, dict, obj->getStream(), out, state);
    } else {
      error(errSyntaxWarning, -1, "Invalid Type 7 shading object");
      goto err1;
    }
    break;
  default:
    error(errSyntaxWarning, -1, "Unimplemented shading type {0:d}", typeA);
    goto err1;
  }

  return shading;

 err1:
  return nullptr;
}

bool GfxShading::init(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) {
  Object obj1;
  int i;

  obj1 = dict->lookup("ColorSpace");
  if (!(colorSpace = GfxColorSpace::parse(res, &obj1, out, state))) {
    error(errSyntaxWarning, -1, "Bad color space in shading dictionary");
    return false;
  }

  for (i = 0; i < gfxColorMaxComps; ++i) {
    background.c[i] = 0;
  }
  hasBackground = false;
  obj1 = dict->lookup("Background");
  if (obj1.isArray()) {
    if (obj1.arrayGetLength() == colorSpace->getNComps()) {
      hasBackground = true;
      for (i = 0; i < colorSpace->getNComps(); ++i) {
	Object obj2 = obj1.arrayGet(i);
	background.c[i] = dblToCol(obj2.getNum());
      }
    } else {
      error(errSyntaxWarning, -1, "Bad Background in shading dictionary");
    }
  }

  bbox_xMin = bbox_yMin = bbox_xMax = bbox_yMax = 0;
  hasBBox = false;
  obj1 = dict->lookup("BBox");
  if (obj1.isArray()) {
    if (obj1.arrayGetLength() == 4) {
      hasBBox = true;
      bbox_xMin = obj1.arrayGet(0).getNum(&hasBBox);
      bbox_yMin = obj1.arrayGet(1).getNum(&hasBBox);
      bbox_xMax = obj1.arrayGet(2).getNum(&hasBBox);
      bbox_yMax = obj1.arrayGet(3).getNum(&hasBBox);
      if (!hasBBox) {
        error(errSyntaxWarning, -1, "Bad BBox in shading dictionary (Values not numbers)");
      }
    } else {
      error(errSyntaxWarning, -1, "Bad BBox in shading dictionary");
    }
  }

  return true;
}

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

GfxFunctionShading::GfxFunctionShading(double x0A, double y0A,
				       double x1A, double y1A,
				       const double *matrixA,
				       Function **funcsA, int nFuncsA):
  GfxShading(1)
{
  int i;

  x0 = x0A;
  y0 = y0A;
  x1 = x1A;
  y1 = y1A;
  for (i = 0; i < 6; ++i) {
    matrix[i] = matrixA[i];
  }
  nFuncs = nFuncsA;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = funcsA[i];
  }
}

GfxFunctionShading::GfxFunctionShading(const GfxFunctionShading *shading):
  GfxShading(shading)
{
  int i;

  x0 = shading->x0;
  y0 = shading->y0;
  x1 = shading->x1;
  y1 = shading->y1;
  for (i = 0; i < 6; ++i) {
    matrix[i] = shading->matrix[i];
  }
  nFuncs = shading->nFuncs;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = shading->funcs[i]->copy();
  }
}

GfxFunctionShading::~GfxFunctionShading() {
  int i;

  for (i = 0; i < nFuncs; ++i) {
    delete funcs[i];
  }
}

GfxFunctionShading *GfxFunctionShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) {
  GfxFunctionShading *shading;
  double x0A, y0A, x1A, y1A;
  double matrixA[6];
  Function *funcsA[gfxColorMaxComps];
  int nFuncsA;
  Object obj1;
  int i;

  x0A = y0A = 0;
  x1A = y1A = 1;
  obj1 = dict->lookup("Domain");
  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
    bool decodeOk = true;
    x0A = obj1.arrayGet(0).getNum(&decodeOk);
    x1A = obj1.arrayGet(1).getNum(&decodeOk);
    y0A = obj1.arrayGet(2).getNum(&decodeOk);
    y1A = obj1.arrayGet(3).getNum(&decodeOk);

    if (!decodeOk) {
      error(errSyntaxWarning, -1, "Invalid Domain array in function shading dictionary");
      return nullptr;
    }
  }

  matrixA[0] = 1; matrixA[1] = 0;
  matrixA[2] = 0; matrixA[3] = 1;
  matrixA[4] = 0; matrixA[5] = 0;
  obj1 = dict->lookup("Matrix");
  if (obj1.isArray() && obj1.arrayGetLength() == 6) {
    bool decodeOk = true;
    matrixA[0] = obj1.arrayGet(0).getNum(&decodeOk);
    matrixA[1] = obj1.arrayGet(1).getNum(&decodeOk);
    matrixA[2] = obj1.arrayGet(2).getNum(&decodeOk);
    matrixA[3] = obj1.arrayGet(3).getNum(&decodeOk);
    matrixA[4] = obj1.arrayGet(4).getNum(&decodeOk);
    matrixA[5] = obj1.arrayGet(5).getNum(&decodeOk);

    if (!decodeOk) {
      error(errSyntaxWarning, -1, "Invalid Matrix array in function shading dictionary");
      return nullptr;
    }
  }

  obj1 = dict->lookup("Function");
  if (obj1.isArray()) {
    nFuncsA = obj1.arrayGetLength();
    if (nFuncsA > gfxColorMaxComps || nFuncsA <= 0) {
      error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary");
      return nullptr;
    }
    for (i = 0; i < nFuncsA; ++i) {
      Object obj2 = obj1.arrayGet(i);
      if (!(funcsA[i] = Function::parse(&obj2))) {
	for (int j = 0; j < i; ++j) {
	  delete funcsA[j];
	}
	return nullptr;
      }
    }
  } else {
    nFuncsA = 1;
    if (!(funcsA[0] = Function::parse(&obj1))) {
      return nullptr;
    }
  }

  shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA,
				   funcsA, nFuncsA);
  if (!shading->init(res, dict, out, state)) {
    delete shading;
    return nullptr;
  }
  return shading;
}

GfxShading *GfxFunctionShading::copy() const {
  return new GfxFunctionShading(this);
}

void GfxFunctionShading::getColor(double x, double y, GfxColor *color) const {
  double in[2], out[gfxColorMaxComps];
  int i;

  // NB: there can be one function with n outputs or n functions with
  // one output each (where n = number of color components)
  for (i = 0; i < gfxColorMaxComps; ++i) {
    out[i] = 0;
  }
  in[0] = x;
  in[1] = y;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i]->transform(in, &out[i]);
  }
  for (i = 0; i < gfxColorMaxComps; ++i) {
    color->c[i] = dblToCol(out[i]);
  }
}

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

GfxUnivariateShading::GfxUnivariateShading(int typeA,
					   double t0A, double t1A,
					   Function **funcsA, int nFuncsA,
					   bool extend0A, bool extend1A):
  GfxShading(typeA)
{
  int i;

  t0 = t0A;
  t1 = t1A;
  nFuncs = nFuncsA;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = funcsA[i];
  }
  extend0 = extend0A;
  extend1 = extend1A;

  cacheSize = 0;
  lastMatch = 0;
  cacheBounds = nullptr;
  cacheCoeff = nullptr;
  cacheValues = nullptr;
}

GfxUnivariateShading::GfxUnivariateShading(const GfxUnivariateShading *shading):
  GfxShading(shading)
{
  int i;

  t0 = shading->t0;
  t1 = shading->t1;
  nFuncs = shading->nFuncs;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = shading->funcs[i]->copy();
  }
  extend0 = shading->extend0;
  extend1 = shading->extend1;

  cacheSize = 0;
  lastMatch = 0;
  cacheBounds = nullptr;
  cacheCoeff = nullptr;
  cacheValues = nullptr;
}

GfxUnivariateShading::~GfxUnivariateShading() {
  int i;

  for (i = 0; i < nFuncs; ++i) {
    delete funcs[i];
  }

  gfree (cacheBounds);
}

int GfxUnivariateShading::getColor(double t, GfxColor *color) {
  double out[gfxColorMaxComps];
  int nComps;

  if (likely(nFuncs >= 1)) {
    // NB: there can be one function with n outputs or n functions with
    // one output each (where n = number of color components)
    nComps = nFuncs * funcs[0]->getOutputSize();
  }

  if (unlikely(nFuncs < 1 || nComps > gfxColorMaxComps)) {
    clearGfxColor(color);
    return gfxColorMaxComps;
  }

  if (cacheSize > 0) {
    double x, ix, *l, *u, *upper;

    if (cacheBounds[lastMatch - 1] >= t) {
      upper = std::lower_bound (cacheBounds, cacheBounds + lastMatch - 1, t);
      lastMatch = upper - cacheBounds;
      lastMatch = std::min<int>(std::max<int>(1, lastMatch), cacheSize - 1);
    } else if (cacheBounds[lastMatch] < t) {
      upper = std::lower_bound (cacheBounds + lastMatch + 1, cacheBounds + cacheSize, t);
      lastMatch = upper - cacheBounds;
      lastMatch = std::min<int>(std::max<int>(1, lastMatch), cacheSize - 1);
    }

    x = (t - cacheBounds[lastMatch-1]) * cacheCoeff[lastMatch];
    ix = 1.0 - x;
    u = cacheValues + lastMatch * nComps;
    l = u - nComps;

    for (int i = 0; i < nComps; ++i) {
      out[i] = ix * l[i] + x * u[i];
    }
  } else {
    for (int i = 0; i < nComps; ++i) {
      out[i] = 0;
    }
    for (int i = 0; i < nFuncs; ++i) {
      if (funcs[i]->getInputSize() != 1) {
        error(errSyntaxWarning, -1, "Invalid shading function (input != 1)");
        break;
      }
      funcs[i]->transform(&t, &out[i]);
    }
  }

  for (int i = 0; i < nComps; ++i) {
    color->c[i] = dblToCol(out[i]);
  }
  return nComps;
}

void GfxUnivariateShading::setupCache(const Matrix *ctm,
				      double xMin, double yMin,
				      double xMax, double yMax) {
  double sMin, sMax, tMin, tMax, upperBound;
  int i, j, nComps, maxSize;

  gfree (cacheBounds);
  cacheBounds = nullptr;
  cacheSize = 0;

  if (unlikely(nFuncs < 1))
    return;

  // NB: there can be one function with n outputs or n functions with
  // one output each (where n = number of color components)
  nComps = nFuncs * funcs[0]->getOutputSize();

  getParameterRange(&sMin, &sMax, xMin, yMin, xMax, yMax);
  upperBound = ctm->norm() * getDistance(sMin, sMax);
  maxSize = ceil(upperBound);
  maxSize = std::max<int>(maxSize, 2);

  {
    double x[4], y[4];

    ctm->transform(xMin, yMin, &x[0], &y[0]);
    ctm->transform(xMax, yMin, &x[1], &y[1]);
    ctm->transform(xMin, yMax, &x[2], &y[2]);
    ctm->transform(xMax, yMax, &x[3], &y[3]);

    xMin = xMax = x[0];
    yMin = yMax = y[0];
    for (i = 1; i < 4; i++) {
      xMin = std::min<double>(xMin, x[i]);
      yMin = std::min<double>(yMin, y[i]);
      xMax = std::max<double>(xMax, x[i]);
      yMax = std::max<double>(yMax, y[i]);
    }
  }

  if (maxSize > (xMax-xMin) * (yMax-yMin)) {
    return;
  }

  if (t0 < t1) {
    tMin = t0 + sMin * (t1 - t0);
    tMax = t0 + sMax * (t1 - t0);
  } else {
    tMin = t0 + sMax * (t1 - t0);
    tMax = t0 + sMin * (t1 - t0);
  }

  cacheBounds = (double *)gmallocn_checkoverflow(maxSize, sizeof(double) * (nComps + 2));
  if (unlikely(!cacheBounds))
    return;
  cacheCoeff = cacheBounds + maxSize;
  cacheValues = cacheCoeff + maxSize;

  if (cacheSize != 0) {
    for (j = 0; j < cacheSize; ++j) {
      cacheCoeff[j] = 1 / (cacheBounds[j+1] - cacheBounds[j]);
    }
  } else if (tMax != tMin) {
    double step = (tMax - tMin) / (maxSize - 1);
    double coeff = (maxSize - 1) / (tMax - tMin);

    cacheSize = maxSize;

    for (j = 0; j < cacheSize; ++j) {
      cacheBounds[j] = tMin + j * step;
      cacheCoeff[j] = coeff;

      for (i = 0; i < nComps; ++i) {
	cacheValues[j*nComps + i] = 0;
      }
      for (i = 0; i < nFuncs; ++i) {
	funcs[i]->transform(&cacheBounds[j], &cacheValues[j*nComps + i]);
      }
    }
  }

  lastMatch = 1;
}


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

GfxAxialShading::GfxAxialShading(double x0A, double y0A,
				 double x1A, double y1A,
				 double t0A, double t1A,
				 Function **funcsA, int nFuncsA,
				 bool extend0A, bool extend1A):
  GfxUnivariateShading(2, t0A, t1A, funcsA, nFuncsA, extend0A, extend1A)
{
  x0 = x0A;
  y0 = y0A;
  x1 = x1A;
  y1 = y1A;
}

GfxAxialShading::GfxAxialShading(const GfxAxialShading *shading):
  GfxUnivariateShading(shading)
{
  x0 = shading->x0;
  y0 = shading->y0;
  x1 = shading->x1;
  y1 = shading->y1;
}

GfxAxialShading::~GfxAxialShading() {
}

GfxAxialShading *GfxAxialShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) {
  GfxAxialShading *shading;
  double x0A, y0A, x1A, y1A;
  double t0A, t1A;
  Function *funcsA[gfxColorMaxComps];
  int nFuncsA;
  bool extend0A, extend1A;
  Object obj1;

  x0A = y0A = x1A = y1A = 0;
  obj1 = dict->lookup("Coords");
  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
    x0A = obj1.arrayGet(0).getNumWithDefaultValue(0);
    y0A = obj1.arrayGet(1).getNumWithDefaultValue(0);
    x1A = obj1.arrayGet(2).getNumWithDefaultValue(0);
    y1A = obj1.arrayGet(3).getNumWithDefaultValue(0);
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid Coords in shading dictionary");
    return nullptr;
  }

  t0A = 0;
  t1A = 1;
  obj1 = dict->lookup("Domain");
  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
    t0A = obj1.arrayGet(0).getNumWithDefaultValue(0);
    t1A = obj1.arrayGet(1).getNumWithDefaultValue(1);
  }

  obj1 = dict->lookup("Function");
  if (obj1.isArray()) {
    nFuncsA = obj1.arrayGetLength();
    if (nFuncsA > gfxColorMaxComps || nFuncsA == 0) {
      error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary");
      return nullptr;
    }
    for (int i = 0; i < nFuncsA; ++i) {
      Object obj2 = obj1.arrayGet(i);
      if (!(funcsA[i] = Function::parse(&obj2))) {
	for (int j = 0; j < i; ++j)
	  delete funcsA[j];
	return nullptr;
      }
    }
  } else {
    nFuncsA = 1;
    if (!(funcsA[0] = Function::parse(&obj1))) {
      return nullptr;
    }
  }

  extend0A = extend1A = false;
  obj1 = dict->lookup("Extend");
  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
    Object obj2 = obj1.arrayGet(0);
    if (obj2.isBool()) {
      extend0A = obj2.getBool();
    } else {
      error(errSyntaxWarning, -1, "Invalid axial shading extend (0)");
    }
    obj2 = obj1.arrayGet(1);
    if (obj2.isBool()) {
      extend1A = obj2.getBool();
    } else {
      error(errSyntaxWarning, -1, "Invalid axial shading extend (1)");
    }
  }

  shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A,
				funcsA, nFuncsA, extend0A, extend1A);
  if (!shading->init(res, dict, out, state)) {
    delete shading;
    shading = nullptr;
  }
  return shading;
}

GfxShading *GfxAxialShading::copy() const {
  return new GfxAxialShading(this);
}

double GfxAxialShading::getDistance(double sMin, double sMax) const {
  double xMin, yMin, xMax, yMax;

  xMin = x0 + sMin * (x1 - x0);
  yMin = y0 + sMin * (y1 - y0);
  xMax = x0 + sMax * (x1 - x0);
  yMax = y0 + sMax * (y1 - y0);

  return hypot(xMax-xMin, yMax-yMin);
}

void GfxAxialShading::getParameterRange(double *lower, double *upper,
					double xMin, double yMin,
					double xMax, double yMax) {
  double pdx, pdy, invsqnorm, tdx, tdy, t, range[2];

  // Linear gradients are orthogonal to the line passing through their
  // extremes. Because of convexity, the parameter range can be
  // computed as the convex hull (one the real line) of the parameter
  // values of the 4 corners of the box.
  //
  // The parameter value t for a point (x,y) can be computed as:
  //
  //   t = (p2 - p1) . (x,y) / |p2 - p1|^2
  //
  // t0  is the t value for the top left corner
  // tdx is the difference between left and right corners
  // tdy is the difference between top and bottom corners

  pdx = x1 - x0;
  pdy = y1 - y0;
  const double invsqnorm_denominator = (pdx * pdx + pdy * pdy);
  if (unlikely(invsqnorm_denominator == 0)) {
    *lower = 0;
    *upper = 0;
    return;
  }
  invsqnorm = 1.0 / invsqnorm_denominator;
  pdx *= invsqnorm;
  pdy *= invsqnorm;

  t = (xMin - x0) * pdx + (yMin - y0) * pdy;
  tdx = (xMax - xMin) * pdx;
  tdy = (yMax - yMin) * pdy;

  // Because of the linearity of the t value, tdx can simply be added
  // the t0 to move along the top edge. After this, *lower and *upper
  // represent the parameter range for the top edge, so extending it
  // to include the whole box simply requires adding tdy to the
  // correct extreme.

  range[0] = range[1] = t;
  if (tdx < 0)
    range[0] += tdx;
  else
    range[1] += tdx;

  if (tdy < 0)
    range[0] += tdy;
  else
    range[1] += tdy;

  *lower = std::max<double>(0., std::min<double>(1., range[0]));
  *upper = std::max<double>(0., std::min<double>(1., range[1]));
}					

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

#ifndef RADIAL_EPSILON
#define RADIAL_EPSILON	(1. / 1024 / 1024)
#endif

GfxRadialShading::GfxRadialShading(double x0A, double y0A, double r0A,
				   double x1A, double y1A, double r1A,
				   double t0A, double t1A,
				   Function **funcsA, int nFuncsA,
				   bool extend0A, bool extend1A):
  GfxUnivariateShading(3, t0A, t1A, funcsA, nFuncsA, extend0A, extend1A)
{
  x0 = x0A;
  y0 = y0A;
  r0 = r0A;
  x1 = x1A;
  y1 = y1A;
  r1 = r1A;
}

GfxRadialShading::GfxRadialShading(const GfxRadialShading *shading):
  GfxUnivariateShading(shading)
{
  x0 = shading->x0;
  y0 = shading->y0;
  r0 = shading->r0;
  x1 = shading->x1;
  y1 = shading->y1;
  r1 = shading->r1;
}

GfxRadialShading::~GfxRadialShading() {
}

GfxRadialShading *GfxRadialShading::parse(GfxResources *res, Dict *dict, OutputDev *out, GfxState *state) {
  GfxRadialShading *shading;
  double x0A, y0A, r0A, x1A, y1A, r1A;
  double t0A, t1A;
  Function *funcsA[gfxColorMaxComps];
  int nFuncsA;
  bool extend0A, extend1A;
  Object obj1;
  int i;

  x0A = y0A = r0A = x1A = y1A = r1A = 0;
  obj1 = dict->lookup("Coords");
  if (obj1.isArray() && obj1.arrayGetLength() == 6) {
    x0A = obj1.arrayGet(0).getNumWithDefaultValue(0);
    y0A = obj1.arrayGet(1).getNumWithDefaultValue(0);
    r0A = obj1.arrayGet(2).getNumWithDefaultValue(0);
    x1A = obj1.arrayGet(3).getNumWithDefaultValue(0);
    y1A = obj1.arrayGet(4).getNumWithDefaultValue(0);
    r1A = obj1.arrayGet(5).getNumWithDefaultValue(0);
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid Coords in shading dictionary");
    return nullptr;
  }

  t0A = 0;
  t1A = 1;
  obj1 = dict->lookup("Domain");
  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
    t0A = obj1.arrayGet(0).getNumWithDefaultValue(0);
    t1A = obj1.arrayGet(1).getNumWithDefaultValue(1);
  }

  obj1 = dict->lookup("Function");
  if (obj1.isArray()) {
    nFuncsA = obj1.arrayGetLength();
    if (nFuncsA > gfxColorMaxComps) {
      error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary");
      return nullptr;
    }
    for (i = 0; i < nFuncsA; ++i) {
      Object obj2 = obj1.arrayGet(i);
      if (!(funcsA[i] = Function::parse(&obj2))) {
	for (int j = 0; j < i; ++j) {
	  delete funcsA[j];
	}
	return nullptr;
      }
    }
  } else {
    nFuncsA = 1;
    if (!(funcsA[0] = Function::parse(&obj1))) {
      return nullptr;
    }
  }

  extend0A = extend1A = false;
  obj1 = dict->lookup("Extend");
  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
    extend0A = obj1.arrayGet(0).getBoolWithDefaultValue(false);
    extend1A = obj1.arrayGet(1).getBoolWithDefaultValue(false);
  }

  shading = new GfxRadialShading(x0A, y0A, r0A, x1A, y1A, r1A, t0A, t1A,
				 funcsA, nFuncsA, extend0A, extend1A);
  if (!shading->init(res, dict, out, state)) {
    delete shading;
    return nullptr;
  }
  return shading;
}

GfxShading *GfxRadialShading::copy() const {
  return new GfxRadialShading(this);
}

double GfxRadialShading::getDistance(double sMin, double sMax) const {
  double xMin, yMin, rMin, xMax, yMax, rMax;

  xMin = x0 + sMin * (x1 - x0);
  yMin = y0 + sMin * (y1 - y0);
  rMin = r0 + sMin * (r1 - r0);

  xMax = x0 + sMax * (x1 - x0);
  yMax = y0 + sMax * (y1 - y0);
  rMax = r0 + sMax * (r1 - r0);

  return hypot(xMax-xMin, yMax-yMin) + fabs(rMax-rMin);
}

// extend range, adapted from cairo, radialExtendRange
static bool
radialExtendRange (double range[2], double value, bool valid)
{
  if (!valid)
    range[0] = range[1] = value;
  else if (value < range[0])
    range[0] = value;
  else if (value > range[1])
    range[1] = value;

  return true;
}

inline void radialEdge(double num, double den, double delta, double lower, double upper,
					   double dr, double mindr, bool &valid, double *range)
{ 
  if (fabs (den) >= RADIAL_EPSILON) {					
    double t_edge, v;							    									
    t_edge = (num) / (den);						
    v = t_edge * (delta);						
    if (t_edge * dr >= mindr && (lower) <= v && v <= (upper))		
      valid = radialExtendRange (range, t_edge, valid);			
  }
}

inline void radialCorner1(double x, double y, double &b, double dx, double dy, double cr, 
					   double dr, double mindr, bool &valid, double *range)
{
    b = (x) * dx + (y) * dy + cr * dr;				
    if (fabs (b) >= RADIAL_EPSILON) {				
      double t_corner;						
      double x2 = (x) * (x);					
      double y2 = (y) * (y);					
      double cr2 = (cr) * (cr);					
      double c = x2 + y2 - cr2;					
								
      t_corner = 0.5 * c / b;					
      if (t_corner * dr >= mindr)				
	valid = radialExtendRange (range, t_corner, valid);	
    }
}

inline void radialCorner2(double x, double y, double a, double &b, double &c, double &d, double dx, double dy, double cr,
					   double inva, double dr, double mindr, bool &valid, double *range)
{
    b = (x) * dx + (y) * dy + cr * dr;				
    c = (x) * (x) + (y) * (y) - cr * cr;			
    d = b * b - a * c;						
    if (d >= 0) {						
      double t_corner;						
								
      d = sqrt (d);						
      t_corner = (b + d) * inva;				
      if (t_corner * dr >= mindr)				
	valid = radialExtendRange (range, t_corner, valid);	
      t_corner = (b - d) * inva;				
      if (t_corner * dr >= mindr)				
	valid = radialExtendRange (range, t_corner, valid);	
    }
}
void GfxRadialShading::getParameterRange(double *lower, double *upper,
					 double xMin, double yMin,
					 double xMax, double yMax) {
  double cx, cy, cr, dx, dy, dr;
  double a, x_focus, y_focus;
  double mindr, minx, miny, maxx, maxy;
  double range[2];
  bool valid;

  // A radial pattern is considered degenerate if it can be
  // represented as a solid or clear pattern.  This corresponds to one
  // of the two cases:
  //
  // 1) The radii are both very small:
  //      |dr| < FLT_EPSILON && min (r0, r1) < FLT_EPSILON
  //
  // 2) The two circles have about the same radius and are very
  //    close to each other (approximately a cylinder gradient that
  //    doesn't move with the parameter):
  //      |dr| < FLT_EPSILON && max (|dx|, |dy|) < 2 * FLT_EPSILON

  if (xMin >= xMax || yMin >=yMax || 
      (fabs (r0 - r1) < RADIAL_EPSILON &&
       (std::min<double>(r0, r1) < RADIAL_EPSILON ||
	std::max<double>(fabs (x0 - x1), fabs (y0 - y1)) < 2 * RADIAL_EPSILON))) {
    *lower = *upper = 0;
    return;
  }

  range[0] = range[1] = 0;
  valid = false;

  x_focus = y_focus = 0; // silence gcc

  cx = x0;
  cy = y0;
  cr = r0;
  dx = x1 - cx;
  dy = y1 - cy;
  dr = r1 - cr;

  // translate by -(cx, cy) to simplify computations
  xMin -= cx;
  yMin -= cy;
  xMax -= cx;
  yMax -= cy;

  // enlarge boundaries slightly to avoid rounding problems in the
  // parameter range computation
  xMin -= RADIAL_EPSILON;
  yMin -= RADIAL_EPSILON;
  xMax += RADIAL_EPSILON;
  yMax += RADIAL_EPSILON;

  // enlarge boundaries even more to avoid rounding problems when
  // testing if a point belongs to the box
  minx = xMin - RADIAL_EPSILON;
  miny = yMin - RADIAL_EPSILON;
  maxx = xMax + RADIAL_EPSILON;
  maxy = yMax + RADIAL_EPSILON;

  // we dont' allow negative radiuses, so we will be checking that
  // t*dr >= mindr to consider t valid
  mindr = -(cr + RADIAL_EPSILON);

  // After the previous transformations, the start circle is centered
  // in the origin and has radius cr. A 1-unit change in the t
  // parameter corresponds to dx,dy,dr changes in the x,y,r of the
  // circle (center coordinates, radius).
  //
  // To compute the minimum range needed to correctly draw the
  // pattern, we start with an empty range and extend it to include
  // the circles touching the bounding box or within it.
    
  // Focus, the point where the circle has radius == 0.
  //
  // r = cr + t * dr = 0
  // t = -cr / dr
  //
  // If the radius is constant (dr == 0) there is no focus (the
  // gradient represents a cylinder instead of a cone).
  if (fabs (dr) >= RADIAL_EPSILON) {
    double t_focus;

    t_focus = -cr / dr;
    x_focus = t_focus * dx;
    y_focus = t_focus * dy;
    if (minx <= x_focus && x_focus <= maxx &&
	miny <= y_focus && y_focus <= maxy)
    {
      valid = radialExtendRange (range, t_focus, valid);
    }
  }

  // Circles externally tangent to box edges.
  //
  // All circles have center in (dx, dy) * t
  //
  // If the circle is tangent to the line defined by the edge of the
  // box, then at least one of the following holds true:
  //
  //   (dx*t) + (cr + dr*t) == x0 (left   edge)
  //   (dx*t) - (cr + dr*t) == x1 (right  edge)
  //   (dy*t) + (cr + dr*t) == y0 (top    edge)
  //   (dy*t) - (cr + dr*t) == y1 (bottom edge)
  //
  // The solution is only valid if the tangent point is actually on
  // the edge, i.e. if its y coordinate is in [y0,y1] for left/right
  // edges and if its x coordinate is in [x0,x1] for top/bottom edges.
  //
  // For the first equation:
  //
  //   (dx + dr) * t = x0 - cr
  //   t = (x0 - cr) / (dx + dr)
  //   y = dy * t
  //
  // in the code this becomes:
  //
  //   t_edge = (num) / (den)
  //   v = (delta) * t_edge
  //
  // If the denominator in t is 0, the pattern is tangent to a line
  // parallel to the edge under examination. The corner-case where the
  // boundary line is the same as the edge is handled by the focus
  // point case and/or by the a==0 case.
  
  // circles tangent (externally) to left/right/top/bottom edge
  radialEdge(xMin - cr, dx + dr, dy, miny, maxy, dr, mindr, valid, range);
  radialEdge(xMax + cr, dx - dr, dy, miny, maxy, dr, mindr, valid, range);
  radialEdge(yMin - cr, dy + dr, dx, minx, maxx, dr, mindr, valid, range);
  radialEdge(yMax + cr, dy - dr, dx, minx, maxx, dr, mindr, valid, range);

  // Circles passing through a corner.
  //
  // A circle passing through the point (x,y) satisfies:
  //
  // (x-t*dx)^2 + (y-t*dy)^2 == (cr + t*dr)^2
  //
  // If we set:
  //   a = dx^2 + dy^2 - dr^2
  //   b = x*dx + y*dy + cr*dr
  //   c = x^2 + y^2 - cr^2
  // we have:
  //   a*t^2 - 2*b*t + c == 0
    
  a = dx * dx + dy * dy - dr * dr;
  if (fabs (a) < RADIAL_EPSILON * RADIAL_EPSILON) {
    double b;

    // Ensure that gradients with both a and dr small are
    // considered degenerate.
    // The floating point version of the degeneracy test implemented
    // in _radial_pattern_is_degenerate() is:
    //
    //  1) The circles are practically the same size:
    //     |dr| < RADIAL_EPSILON
    //  AND
    //  2a) The circles are both very small:
    //      min (r0, r1) < RADIAL_EPSILON
    //   OR
    //  2b) The circles are very close to each other:
    //      max (|dx|, |dy|) < 2 * RADIAL_EPSILON
    //
    // Assuming that the gradient is not degenerate, we want to
    // show that |a| < RADIAL_EPSILON^2 implies |dr| >= RADIAL_EPSILON.
    //
    // If the gradient is not degenerate yet it has |dr| <
    // RADIAL_EPSILON, (2b) is false, thus:
    //
    //   max (|dx|, |dy|) >= 2*RADIAL_EPSILON
    // which implies:
    //   4*RADIAL_EPSILON^2 <= max (|dx|, |dy|)^2 <= dx^2 + dy^2
    //
    // From the definition of a, we get:
    //   a = dx^2 + dy^2 - dr^2 < RADIAL_EPSILON^2
    //   dx^2 + dy^2 - RADIAL_EPSILON^2 < dr^2
    //   3*RADIAL_EPSILON^2 < dr^2
    //
    // which is inconsistent with the hypotheses, thus |dr| <
    // RADIAL_EPSILON is false or the gradient is degenerate.
	
    assert (fabs (dr) >= RADIAL_EPSILON);

    // If a == 0, all the circles are tangent to a line in the
    // focus point. If this line is within the box extents, we
    // should add the circle with infinite radius, but this would
    // make the range unbounded. We will be limiting the range to
    // [0,1] anyway, so we simply add the biggest legitimate
    // circle (it happens for 0 or for 1).
    if (dr < 0) {
      valid = radialExtendRange (range, 0, valid);
    } else {
      valid = radialExtendRange (range, 1, valid);
    }

    // Nondegenerate, nonlimit circles passing through the corners.
    //
    // a == 0 && a*t^2 - 2*b*t + c == 0
    //
    // t = c / (2*b)
    //
    // The b == 0 case has just been handled, so we only have to
    // compute this if b != 0.

	// circles touching each corner
	radialCorner1(xMin, yMin, b, dx, dy, cr, dr, mindr, valid, range);
	radialCorner1(xMin, yMax, b, dx, dy, cr, dr, mindr, valid, range);
	radialCorner1(xMax, yMin, b, dx, dy, cr, dr, mindr, valid, range);
	radialCorner1(xMax, yMax, b, dx, dy, cr, dr, mindr, valid, range);
  } else {
    double inva, b, c, d;

    inva = 1 / a;

    // Nondegenerate, nonlimit circles passing through the corners.
    //
    // a != 0 && a*t^2 - 2*b*t + c == 0
    //
    // t = (b +- sqrt (b*b - a*c)) / a
    //
    // If the argument of sqrt() is negative, then no circle
    // passes through the corner.

    // circles touching each corner
	radialCorner2(xMin, yMin, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
	radialCorner2(xMin, yMax, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
	radialCorner2(xMax, yMin, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
	radialCorner2(xMax, yMax, a, b, c, d, dx, dy, cr, inva, dr, mindr, valid, range);
  }

  *lower = std::max<double>(0., std::min<double>(1., range[0]));
  *upper = std::max<double>(0., std::min<double>(1., range[1]));
}

//------------------------------------------------------------------------
// GfxShadingBitBuf
//------------------------------------------------------------------------

class GfxShadingBitBuf {
public:

  GfxShadingBitBuf(Stream *strA);
  ~GfxShadingBitBuf();
  GfxShadingBitBuf(const GfxShadingBitBuf &) = delete;
  GfxShadingBitBuf& operator=(const GfxShadingBitBuf &) = delete;
  bool getBits(int n, unsigned int *val);
  void flushBits();

private:

  Stream *str;
  int bitBuf;
  int nBits;
};

GfxShadingBitBuf::GfxShadingBitBuf(Stream *strA) {
  str = strA;
  str->reset();
  bitBuf = 0;
  nBits = 0;
}

GfxShadingBitBuf::~GfxShadingBitBuf() {
  str->close();
}

bool GfxShadingBitBuf::getBits(int n, unsigned int *val) {
  unsigned int x;

  if (nBits >= n) {
    x = (bitBuf >> (nBits - n)) & ((1 << n) - 1);
    nBits -= n;
  } else {
    x = 0;
    if (nBits > 0) {
      x = bitBuf & ((1 << nBits) - 1);
      n -= nBits;
      nBits = 0;
    }
    while (n > 0) {
      if ((bitBuf = str->getChar()) == EOF) {
	nBits = 0;
	return false;
      }
      if (n >= 8) {
	x = (x << 8) | bitBuf;
	n -= 8;
      } else {
	x = (x << n) | (bitBuf >> (8 - n));
	nBits = 8 - n;
	n = 0;
      }
    }
  }
  *val = x;
  return true;
}

void GfxShadingBitBuf::flushBits() {
  bitBuf = 0;
  nBits = 0;
}

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

GfxGouraudTriangleShading::GfxGouraudTriangleShading(
			       int typeA,
			       GfxGouraudVertex *verticesA, int nVerticesA,
			       int (*trianglesA)[3], int nTrianglesA,
			       Function **funcsA, int nFuncsA):
  GfxShading(typeA)
{
  int i;

  vertices = verticesA;
  nVertices = nVerticesA;
  triangles = trianglesA;
  nTriangles = nTrianglesA;
  nFuncs = nFuncsA;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = funcsA[i];
  }
}

GfxGouraudTriangleShading::GfxGouraudTriangleShading(
			       const GfxGouraudTriangleShading *shading):
  GfxShading(shading)
{
  int i;

  nVertices = shading->nVertices;
  vertices = (GfxGouraudVertex *)gmallocn(nVertices, sizeof(GfxGouraudVertex));
  memcpy(vertices, shading->vertices, nVertices * sizeof(GfxGouraudVertex));
  nTriangles = shading->nTriangles;
  triangles = (int (*)[3])gmallocn(nTriangles * 3, sizeof(int));
  memcpy(triangles, shading->triangles, nTriangles * 3 * sizeof(int));
  nFuncs = shading->nFuncs;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = shading->funcs[i]->copy();
  }
}

GfxGouraudTriangleShading::~GfxGouraudTriangleShading() {
  int i;

  gfree(vertices);
  gfree(triangles);
  for (i = 0; i < nFuncs; ++i) {
    delete funcs[i];
  }
}

GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(GfxResources *res, int typeA,
							    Dict *dict,
							    Stream *str,
							    OutputDev *out, GfxState *gfxState) {
  GfxGouraudTriangleShading *shading;
  Function *funcsA[gfxColorMaxComps];
  int nFuncsA;
  int coordBits, compBits, flagBits, vertsPerRow, nRows;
  double xMin, xMax, yMin, yMax;
  double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps];
  double xMul, yMul;
  double cMul[gfxColorMaxComps];
  GfxGouraudVertex *verticesA;
  int (*trianglesA)[3];
  int nComps, nVerticesA, nTrianglesA, vertSize, triSize;
  unsigned int x, y, flag;
  unsigned int c[gfxColorMaxComps];
  GfxShadingBitBuf *bitBuf;
  Object obj1;
  int i, j, k, state;

  obj1 = dict->lookup("BitsPerCoordinate");
  if (obj1.isInt()) {
    coordBits = obj1.getInt();
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid BitsPerCoordinate in shading dictionary");
    return nullptr;
  }
  if (unlikely(coordBits <= 0)) {
    error(errSyntaxWarning, -1, "Invalid BitsPerCoordinate in shading dictionary");
    return nullptr;
  }
  obj1 = dict->lookup("BitsPerComponent");
  if (obj1.isInt()) {
    compBits = obj1.getInt();
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid BitsPerComponent in shading dictionary");
    return nullptr;
  }
  if (unlikely(compBits <= 0 || compBits > 31)) {
    error(errSyntaxWarning, -1, "Invalid BitsPerComponent in shading dictionary");
    return nullptr;
  }
  flagBits = vertsPerRow = 0; // make gcc happy
  if (typeA == 4) {
    obj1 = dict->lookup("BitsPerFlag");
  if (obj1.isInt()) {
      flagBits = obj1.getInt();
    } else {
      error(errSyntaxWarning, -1, "Missing or invalid BitsPerFlag in shading dictionary");
      return nullptr;
    }
  } else {
    obj1 = dict->lookup("VerticesPerRow");
  if (obj1.isInt()) {
      vertsPerRow = obj1.getInt();
    } else {
      error(errSyntaxWarning, -1, "Missing or invalid VerticesPerRow in shading dictionary");
      return nullptr;
    }
  }
  obj1 = dict->lookup("Decode");
  if (obj1.isArray() && obj1.arrayGetLength() >= 6) {
    bool decodeOk = true;
    xMin = obj1.arrayGet(0).getNum(&decodeOk);
    xMax = obj1.arrayGet(1).getNum(&decodeOk);
    xMul = (xMax - xMin) / (pow(2.0, coordBits) - 1);
    yMin = obj1.arrayGet(2).getNum(&decodeOk);
    yMax = obj1.arrayGet(3).getNum(&decodeOk);
    yMul = (yMax - yMin) / (pow(2.0, coordBits) - 1);
    for (i = 0; 5 + 2*i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) {
      cMin[i] = obj1.arrayGet(4 + 2*i).getNum(&decodeOk);
      cMax[i] = obj1.arrayGet(5 + 2*i).getNum(&decodeOk);
      cMul[i] = (cMax[i] - cMin[i]) / (double)((1u << compBits) - 1);
    }
    nComps = i;

    if (!decodeOk) {
      error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary");
      return nullptr;
    }
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary");
    return nullptr;
  }

  obj1 = dict->lookup("Function");
  if (!obj1.isNull()) {
    if (obj1.isArray()) {
      nFuncsA = obj1.arrayGetLength();
      if (nFuncsA > gfxColorMaxComps) {
	error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary");
	return nullptr;
      }
      for (i = 0; i < nFuncsA; ++i) {
	Object obj2 = obj1.arrayGet(i);
	if (!(funcsA[i] = Function::parse(&obj2))) {
	  return nullptr;
	}
      }
    } else {
      nFuncsA = 1;
      if (!(funcsA[0] = Function::parse(&obj1))) {
	return nullptr;
      }
    }
  } else {
    nFuncsA = 0;
  }

  nVerticesA = nTrianglesA = 0;
  verticesA = nullptr;
  trianglesA = nullptr;
  vertSize = triSize = 0;
  state = 0;
  flag = 0; // make gcc happy
  bitBuf = new GfxShadingBitBuf(str);
  while (true) {
    if (typeA == 4) {
      if (!bitBuf->getBits(flagBits, &flag)) {
	break;
      }
    }
    if (!bitBuf->getBits(coordBits, &x) ||
	!bitBuf->getBits(coordBits, &y)) {
      break;
    }
    for (i = 0; i < nComps; ++i) {
      if (!bitBuf->getBits(compBits, &c[i])) {
	break;
      }
    }
    if (i < nComps) {
      break;
    }
    if (nVerticesA == vertSize) {
      int oldVertSize = vertSize;
      vertSize = (vertSize == 0) ? 16 : 2 * vertSize;
      verticesA = (GfxGouraudVertex *)
	              greallocn_checkoverflow(verticesA, vertSize, sizeof(GfxGouraudVertex));
      if (unlikely(!verticesA)) {
        error(errSyntaxWarning, -1, "GfxGouraudTriangleShading::parse: vertices size overflow");
        gfree(trianglesA);
        delete bitBuf;
        return nullptr;
      }
      memset(verticesA + oldVertSize, 0, (vertSize - oldVertSize) * sizeof(GfxGouraudVertex));
    }
    verticesA[nVerticesA].x = xMin + xMul * (double)x;
    verticesA[nVerticesA].y = yMin + yMul * (double)y;
    for (i = 0; i < nComps; ++i) {
      verticesA[nVerticesA].color.c[i] =
	  dblToCol(cMin[i] + cMul[i] * (double)c[i]);
    }
    ++nVerticesA;
    bitBuf->flushBits();
    if (typeA == 4) {
      if (state == 0 || state == 1) {
	++state;
      } else if (state == 2 || flag > 0) {
	if (nTrianglesA == triSize) {
	  triSize = (triSize == 0) ? 16 : 2 * triSize;
	  trianglesA = (int (*)[3])
	                   greallocn(trianglesA, triSize * 3, sizeof(int));
	}
	if (state == 2) {
	  trianglesA[nTrianglesA][0] = nVerticesA - 3;
	  trianglesA[nTrianglesA][1] = nVerticesA - 2;
	  trianglesA[nTrianglesA][2] = nVerticesA - 1;
	  ++state;
	} else if (flag == 1) {
	  trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][1];
	  trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2];
	  trianglesA[nTrianglesA][2] = nVerticesA - 1;
	} else { // flag == 2
	  trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][0];
	  trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2];
	  trianglesA[nTrianglesA][2] = nVerticesA - 1;
	}
	++nTrianglesA;
      } else { // state == 3 && flag == 0
	state = 1;
      }
    }
  }
  delete bitBuf;
  if (typeA == 5 && nVerticesA > 0 && vertsPerRow > 0) {
    nRows = nVerticesA / vertsPerRow;
    nTrianglesA = (nRows - 1) * 2 * (vertsPerRow - 1);
    trianglesA = (int (*)[3])gmallocn_checkoverflow(nTrianglesA * 3, sizeof(int));
    if (unlikely(!trianglesA)) {
      gfree(verticesA);
      for (i = 0; i < nFuncsA; ++i) {
	delete funcsA[i];
      }
      return nullptr;
    }
    k = 0;
    for (i = 0; i < nRows - 1; ++i) {
      for (j = 0; j < vertsPerRow - 1; ++j) {
	trianglesA[k][0] = i * vertsPerRow + j;
	trianglesA[k][1] = i * vertsPerRow + j+1;
	trianglesA[k][2] = (i+1) * vertsPerRow + j;
	++k;
	trianglesA[k][0] = i * vertsPerRow + j+1;
	trianglesA[k][1] = (i+1) * vertsPerRow + j;
	trianglesA[k][2] = (i+1) * vertsPerRow + j+1;
	++k;
      }
    }
  }

  shading = new GfxGouraudTriangleShading(typeA, verticesA, nVerticesA,
					  trianglesA, nTrianglesA,
					  funcsA, nFuncsA);
  if (!shading->init(res, dict, out, gfxState)) {
    delete shading;
    return nullptr;
  }
  return shading;
}

GfxShading *GfxGouraudTriangleShading::copy() const {
  return new GfxGouraudTriangleShading(this);
}

void GfxGouraudTriangleShading::getTriangle(
				    int i,
				    double *x0, double *y0, GfxColor *color0,
				    double *x1, double *y1, GfxColor *color1,
				    double *x2, double *y2, GfxColor *color2) {
  int v;

  assert(!isParameterized()); 

  v = triangles[i][0];
  *x0 = vertices[v].x;
  *y0 = vertices[v].y;
  *color0 = vertices[v].color;
  v = triangles[i][1];
  *x1 = vertices[v].x;
  *y1 = vertices[v].y;
  *color1 = vertices[v].color;
  v = triangles[i][2];
  *x2 = vertices[v].x;
  *y2 = vertices[v].y;
  *color2 = vertices[v].color;
}

void GfxGouraudTriangleShading::getParameterizedColor(double t, GfxColor *color) const {
  double out[gfxColorMaxComps];

  for (int j = 0; j < nFuncs; ++j) {
    funcs[j]->transform(&t, &out[j]);
  }
  for (int j = 0; j < gfxColorMaxComps; ++j) {
    color->c[j] = dblToCol(out[j]);
  }
}

void GfxGouraudTriangleShading::getTriangle(int i,
                                            double *x0, double *y0, double *color0,
                                            double *x1, double *y1, double *color1,
                                            double *x2, double *y2, double *color2) {
  int v;

  assert(isParameterized()); 

  v = triangles[i][0];
  if (likely(v >= 0 && v < nVertices)) {
    *x0 = vertices[v].x;
    *y0 = vertices[v].y;
    *color0 = colToDbl(vertices[v].color.c[0]);
  }
  v = triangles[i][1];
  if (likely(v >= 0 && v < nVertices)) {
    *x1 = vertices[v].x;
    *y1 = vertices[v].y;
    *color1 = colToDbl(vertices[v].color.c[0]);
  }
  v = triangles[i][2];
  if (likely(v >= 0 && v < nVertices)) {
    *x2 = vertices[v].x;
    *y2 = vertices[v].y;
    *color2 = colToDbl(vertices[v].color.c[0]);
  }
}

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

GfxPatchMeshShading::GfxPatchMeshShading(int typeA,
					 GfxPatch *patchesA, int nPatchesA,
					 Function **funcsA, int nFuncsA):
  GfxShading(typeA)
{
  int i;

  patches = patchesA;
  nPatches = nPatchesA;
  nFuncs = nFuncsA;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = funcsA[i];
  }
}

GfxPatchMeshShading::GfxPatchMeshShading(const GfxPatchMeshShading *shading):
  GfxShading(shading)
{
  int i;

  nPatches = shading->nPatches;
  patches = (GfxPatch *)gmallocn(nPatches, sizeof(GfxPatch));
  memcpy(patches, shading->patches, nPatches * sizeof(GfxPatch));
  nFuncs = shading->nFuncs;
  for (i = 0; i < nFuncs; ++i) {
    funcs[i] = shading->funcs[i]->copy();
  }
}

GfxPatchMeshShading::~GfxPatchMeshShading() {
  int i;

  gfree(patches);
  for (i = 0; i < nFuncs; ++i) {
    delete funcs[i];
  }
}

GfxPatchMeshShading *GfxPatchMeshShading::parse(GfxResources *res, int typeA, Dict *dict,
						Stream *str, OutputDev *out, GfxState *state) {
  GfxPatchMeshShading *shading;
  Function *funcsA[gfxColorMaxComps];
  int nFuncsA;
  int coordBits, compBits, flagBits;
  double xMin, xMax, yMin, yMax;
  double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps];
  double xMul, yMul;
  double cMul[gfxColorMaxComps];
  GfxPatch *patchesA, *p;
  int nComps, nPatchesA, patchesSize, nPts, nColors;
  unsigned int flag;
  double x[16], y[16];
  unsigned int xi, yi;
  double c[4][gfxColorMaxComps];
  unsigned int ci;
  Object obj1;
  int i, j;

  obj1 = dict->lookup("BitsPerCoordinate");
  if (obj1.isInt()) {
    coordBits = obj1.getInt();
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid BitsPerCoordinate in shading dictionary");
    return nullptr;
  }
  if (unlikely(coordBits <= 0)) {
    error(errSyntaxWarning, -1, "Invalid BitsPerCoordinate in shading dictionary");
    return nullptr;
  }
  obj1 = dict->lookup("BitsPerComponent");
  if (obj1.isInt()) {
    compBits = obj1.getInt();
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid BitsPerComponent in shading dictionary");
    return nullptr;
  }
  if (unlikely(compBits <= 0 || compBits > 31)) {
    error(errSyntaxWarning, -1, "Invalid BitsPerComponent in shading dictionary");
    return nullptr;
  }
  obj1 = dict->lookup("BitsPerFlag");
  if (obj1.isInt()) {
    flagBits = obj1.getInt();
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid BitsPerFlag in shading dictionary");
    return nullptr;
  }
  obj1 = dict->lookup("Decode");
  if (obj1.isArray() && obj1.arrayGetLength() >= 6) {
    bool decodeOk = true;
    xMin = obj1.arrayGet(0).getNum(&decodeOk);
    xMax = obj1.arrayGet(1).getNum(&decodeOk);
    xMul = (xMax - xMin) / (pow(2.0, coordBits) - 1);
    yMin = obj1.arrayGet(2).getNum(&decodeOk);
    yMax = obj1.arrayGet(3).getNum(&decodeOk);
    yMul = (yMax - yMin) / (pow(2.0, coordBits) - 1);
    for (i = 0; 5 + 2*i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) {
      cMin[i] = obj1.arrayGet(4 + 2*i).getNum(&decodeOk);
      cMax[i] = obj1.arrayGet(5 + 2*i).getNum(&decodeOk);
      cMul[i] = (cMax[i] - cMin[i]) / (double)((1u << compBits) - 1);
    }
    nComps = i;

    if (!decodeOk) {
      error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary");
      return nullptr;
    }
  } else {
    error(errSyntaxWarning, -1, "Missing or invalid Decode array in shading dictionary");
    return nullptr;
  }

  obj1 = dict->lookup("Function");
  if (!obj1.isNull()) {
    if (obj1.isArray()) {
      nFuncsA = obj1.arrayGetLength();
      if (nFuncsA > gfxColorMaxComps) {
	error(errSyntaxWarning, -1, "Invalid Function array in shading dictionary");
	return nullptr;
      }
      for (i = 0; i < nFuncsA; ++i) {
	Object obj2 = obj1.arrayGet(i);
	if (!(funcsA[i] = Function::parse(&obj2))) {
	  return nullptr;
	}
      }
    } else {
      nFuncsA = 1;
      if (!(funcsA[0] = Function::parse(&obj1))) {
	return nullptr;
      }
    }
  } else {
    nFuncsA = 0;
  }

  nPatchesA = 0;
  patchesA = nullptr;
  patchesSize = 0;
  auto bitBuf = std::make_unique<GfxShadingBitBuf>(str);
  while (true) {
    if (!bitBuf->getBits(flagBits, &flag)) {
      break;
    }
    if (typeA == 6) {
      switch (flag) {
      case 0: nPts = 12; nColors = 4; break;
      case 1:
      case 2:
      case 3:
      default: nPts =  8; nColors = 2; break;
      }
    } else {
      switch (flag) {
      case 0: nPts = 16; nColors = 4; break;
      case 1:
      case 2:
      case 3:
      default: nPts = 12; nColors = 2; break;
      }
    }
    for (i = 0; i < nPts; ++i) {
      if (!bitBuf->getBits(coordBits, &xi) ||
	  !bitBuf->getBits(coordBits, &yi)) {
	break;
      }
      x[i] = xMin + xMul * (double)xi;
      y[i] = yMin + yMul * (double)yi;
    }
    if (i < nPts) {
      break;
    }
    for (i = 0; i < nColors; ++i) {
      for (j = 0; j < nComps; ++j) {
	if (!bitBuf->getBits(compBits, &ci)) {
	  break;
	}
	c[i][j] = cMin[j] + cMul[j] * (double)ci;
	if( nFuncsA == 0 ) {
	  // ... and colorspace values can also be stored into doubles.
	  // They will be casted later.
	  c[i][j] = dblToCol(c[i][j]);
	}
      }
      if (j < nComps) {
	break;
      }
    }
    if (i < nColors) {
      break;
    }
    if (nPatchesA == patchesSize) {
      int oldPatchesSize = patchesSize;
      patchesSize = (patchesSize == 0) ? 16 : 2 * patchesSize;
      patchesA = (GfxPatch *)greallocn_checkoverflow(patchesA,
				       patchesSize, sizeof(GfxPatch));
      if (unlikely(!patchesA)) {
	for (int k = 0; k < nFuncsA; ++k) delete funcsA[k];
	return nullptr;
      }
      memset(patchesA + oldPatchesSize, 0, (patchesSize - oldPatchesSize) * sizeof(GfxPatch));
    }
    p = &patchesA[nPatchesA];
    if (typeA == 6) {
      switch (flag) {
      case 0:
	p->x[0][0] = x[0];
	p->y[0][0] = y[0];
	p->x[0][1] = x[1];
	p->y[0][1] = y[1];
	p->x[0][2] = x[2];
	p->y[0][2] = y[2];
	p->x[0][3] = x[3];
	p->y[0][3] = y[3];
	p->x[1][3] = x[4];
	p->y[1][3] = y[4];
	p->x[2][3] = x[5];
	p->y[2][3] = y[5];
	p->x[3][3] = x[6];
	p->y[3][3] = y[6];
	p->x[3][2] = x[7];
	p->y[3][2] = y[7];
	p->x[3][1] = x[8];
	p->y[3][1] = y[8];
	p->x[3][0] = x[9];
	p->y[3][0] = y[9];
	p->x[2][0] = x[10];
	p->y[2][0] = y[10];
	p->x[1][0] = x[11];
	p->y[1][0] = y[11];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = c[0][j];
	  p->color[0][1].c[j] = c[1][j];
	  p->color[1][1].c[j] = c[2][j];
	  p->color[1][0].c[j] = c[3][j];
	}
	break;
      case 1:
	if (nPatchesA == 0) {
          gfree(patchesA);
	  for (int k = 0; k < nFuncsA; ++k) delete funcsA[k];
	  return nullptr;
	}
	p->x[0][0] = patchesA[nPatchesA-1].x[0][3];
	p->y[0][0] = patchesA[nPatchesA-1].y[0][3];
	p->x[0][1] = patchesA[nPatchesA-1].x[1][3];
	p->y[0][1] = patchesA[nPatchesA-1].y[1][3];
	p->x[0][2] = patchesA[nPatchesA-1].x[2][3];
	p->y[0][2] = patchesA[nPatchesA-1].y[2][3];
	p->x[0][3] = patchesA[nPatchesA-1].x[3][3];
	p->y[0][3] = patchesA[nPatchesA-1].y[3][3];
	p->x[1][3] = x[0];
	p->y[1][3] = y[0];
	p->x[2][3] = x[1];
	p->y[2][3] = y[1];
	p->x[3][3] = x[2];
	p->y[3][3] = y[2];
	p->x[3][2] = x[3];
	p->y[3][2] = y[3];
	p->x[3][1] = x[4];
	p->y[3][1] = y[4];
	p->x[3][0] = x[5];
	p->y[3][0] = y[5];
	p->x[2][0] = x[6];
	p->y[2][0] = y[6];
	p->x[1][0] = x[7];
	p->y[1][0] = y[7];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j];
	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
	  p->color[1][1].c[j] = c[0][j];
	  p->color[1][0].c[j] = c[1][j];
	}
	break;
      case 2:
	if (nPatchesA == 0) {
          gfree(patchesA);
	  for (int k = 0; k < nFuncsA; ++k) delete funcsA[k];
	  return nullptr;
	}
	p->x[0][0] = patchesA[nPatchesA-1].x[3][3];
	p->y[0][0] = patchesA[nPatchesA-1].y[3][3];
	p->x[0][1] = patchesA[nPatchesA-1].x[3][2];
	p->y[0][1] = patchesA[nPatchesA-1].y[3][2];
	p->x[0][2] = patchesA[nPatchesA-1].x[3][1];
	p->y[0][2] = patchesA[nPatchesA-1].y[3][1];
	p->x[0][3] = patchesA[nPatchesA-1].x[3][0];
	p->y[0][3] = patchesA[nPatchesA-1].y[3][0];
	p->x[1][3] = x[0];
	p->y[1][3] = y[0];
	p->x[2][3] = x[1];
	p->y[2][3] = y[1];
	p->x[3][3] = x[2];
	p->y[3][3] = y[2];
	p->x[3][2] = x[3];
	p->y[3][2] = y[3];
	p->x[3][1] = x[4];
	p->y[3][1] = y[4];
	p->x[3][0] = x[5];
	p->y[3][0] = y[5];
	p->x[2][0] = x[6];
	p->y[2][0] = y[6];
	p->x[1][0] = x[7];
	p->y[1][0] = y[7];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
	  p->color[1][1].c[j] = c[0][j];
	  p->color[1][0].c[j] = c[1][j];
	}
	break;
      case 3:
	if (nPatchesA == 0) {
          gfree(patchesA);
	  for (int k = 0; k < nFuncsA; ++k) delete funcsA[k];
	  return nullptr;
	}
	p->x[0][0] = patchesA[nPatchesA-1].x[3][0];
	p->y[0][0] = patchesA[nPatchesA-1].y[3][0];
	p->x[0][1] = patchesA[nPatchesA-1].x[2][0];
	p->y[0][1] = patchesA[nPatchesA-1].y[2][0];
	p->x[0][2] = patchesA[nPatchesA-1].x[1][0];
	p->y[0][2] = patchesA[nPatchesA-1].y[1][0];
	p->x[0][3] = patchesA[nPatchesA-1].x[0][0];
	p->y[0][3] = patchesA[nPatchesA-1].y[0][0];
	p->x[1][3] = x[0];
	p->y[1][3] = y[0];
	p->x[2][3] = x[1];
	p->y[2][3] = y[1];
	p->x[3][3] = x[2];
	p->y[3][3] = y[2];
	p->x[3][2] = x[3];
	p->y[3][2] = y[3];
	p->x[3][1] = x[4];
	p->y[3][1] = y[4];
	p->x[3][0] = x[5];
	p->y[3][0] = y[5];
	p->x[2][0] = x[6];
	p->y[2][0] = y[6];
	p->x[1][0] = x[7];
	p->y[1][0] = y[7];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j];
	  p->color[1][1].c[j] = c[0][j];
	  p->color[1][0].c[j] = c[1][j];
	}
	break;
  }
    } else {
      switch (flag) {
      case 0:
	p->x[0][0] = x[0];
	p->y[0][0] = y[0];
	p->x[0][1] = x[1];
	p->y[0][1] = y[1];
	p->x[0][2] = x[2];
	p->y[0][2] = y[2];
	p->x[0][3] = x[3];
	p->y[0][3] = y[3];
	p->x[1][3] = x[4];
	p->y[1][3] = y[4];
	p->x[2][3] = x[5];
	p->y[2][3] = y[5];
	p->x[3][3] = x[6];
	p->y[3][3] = y[6];
	p->x[3][2] = x[7];
	p->y[3][2] = y[7];
	p->x[3][1] = x[8];
	p->y[3][1] = y[8];
	p->x[3][0] = x[9];
	p->y[3][0] = y[9];
	p->x[2][0] = x[10];
	p->y[2][0] = y[10];
	p->x[1][0] = x[11];
	p->y[1][0] = y[11];
	p->x[1][1] = x[12];
	p->y[1][1] = y[12];
	p->x[1][2] = x[13];
	p->y[1][2] = y[13];
	p->x[2][2] = x[14];
	p->y[2][2] = y[14];
	p->x[2][1] = x[15];
	p->y[2][1] = y[15];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = c[0][j];
	  p->color[0][1].c[j] = c[1][j];
	  p->color[1][1].c[j] = c[2][j];
	  p->color[1][0].c[j] = c[3][j];
	}
	break;
      case 1:
	if (nPatchesA == 0) {
          gfree(patchesA);
	  for (int k = 0; k < nFuncsA; ++k) delete funcsA[k];
	  return nullptr;
	}
	p->x[0][0] = patchesA[nPatchesA-1].x[0][3];
	p->y[0][0] = patchesA[nPatchesA-1].y[0][3];
	p->x[0][1] = patchesA[nPatchesA-1].x[1][3];
	p->y[0][1] = patchesA[nPatchesA-1].y[1][3];
	p->x[0][2] = patchesA[nPatchesA-1].x[2][3];
	p->y[0][2] = patchesA[nPatchesA-1].y[2][3];
	p->x[0][3] = patchesA[nPatchesA-1].x[3][3];
	p->y[0][3] = patchesA[nPatchesA-1].y[3][3];
	p->x[1][3] = x[0];
	p->y[1][3] = y[0];
	p->x[2][3] = x[1];
	p->y[2][3] = y[1];
	p->x[3][3] = x[2];
	p->y[3][3] = y[2];
	p->x[3][2] = x[3];
	p->y[3][2] = y[3];
	p->x[3][1] = x[4];
	p->y[3][1] = y[4];
	p->x[3][0] = x[5];
	p->y[3][0] = y[5];
	p->x[2][0] = x[6];
	p->y[2][0] = y[6];
	p->x[1][0] = x[7];
	p->y[1][0] = y[7];
	p->x[1][1] = x[8];
	p->y[1][1] = y[8];
	p->x[1][2] = x[9];
	p->y[1][2] = y[9];
	p->x[2][2] = x[10];
	p->y[2][2] = y[10];
	p->x[2][1] = x[11];
	p->y[2][1] = y[11];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j];
	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
	  p->color[1][1].c[j] = c[0][j];
	  p->color[1][0].c[j] = c[1][j];
	}
	break;
      case 2:
	if (nPatchesA == 0) {
          gfree(patchesA);
	  for (int k = 0; k < nFuncsA; ++k) delete funcsA[k];
	  return nullptr;
	}
	p->x[0][0] = patchesA[nPatchesA-1].x[3][3];
	p->y[0][0] = patchesA[nPatchesA-1].y[3][3];
	p->x[0][1] = patchesA[nPatchesA-1].x[3][2];
	p->y[0][1] = patchesA[nPatchesA-1].y[3][2];
	p->x[0][2] = patchesA[nPatchesA-1].x[3][1];
	p->y[0][2] = patchesA[nPatchesA-1].y[3][1];
	p->x[0][3] = patchesA[nPatchesA-1].x[3][0];
	p->y[0][3] = patchesA[nPatchesA-1].y[3][0];
	p->x[1][3] = x[0];
	p->y[1][3] = y[0];
	p->x[2][3] = x[1];
	p->y[2][3] = y[1];
	p->x[3][3] = x[2];
	p->y[3][3] = y[2];
	p->x[3][2] = x[3];
	p->y[3][2] = y[3];
	p->x[3][1] = x[4];
	p->y[3][1] = y[4];
	p->x[3][0] = x[5];
	p->y[3][0] = y[5];
	p->x[2][0] = x[6];
	p->y[2][0] = y[6];
	p->x[1][0] = x[7];
	p->y[1][0] = y[7];
	p->x[1][1] = x[8];
	p->y[1][1] = y[8];
	p->x[1][2] = x[9];
	p->y[1][2] = y[9];
	p->x[2][2] = x[10];
	p->y[2][2] = y[10];
	p->x[2][1] = x[11];
	p->y[2][1] = y[11];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
	  p->color[1][1].c[j] = c[0][j];
	  p->color[1][0].c[j] = c[1][j];
	}
	break;
      case 3:
	if (nPatchesA == 0) {
          gfree(patchesA);
	  for (int k = 0; k < nFuncsA; ++k) delete funcsA[k];
	  return nullptr;
	}
	p->x[0][0] = patchesA[nPatchesA-1].x[3][0];
	p->y[0][0] = patchesA[nPatchesA-1].y[3][0];
	p->x[0][1] = patchesA[nPatchesA-1].x[2][0];
	p->y[0][1] = patchesA[nPatchesA-1].y[2][0];
	p->x[0][2] = patchesA[nPatchesA-1].x[1][0];
	p->y[0][2] = patchesA[nPatchesA-1].y[1][0];
	p->x[0][3] = patchesA[nPatchesA-1].x[0][0];
	p->y[0][3] = patchesA[nPatchesA-1].y[0][0];
	p->x[1][3] = x[0];
	p->y[1][3] = y[0];
	p->x[2][3] = x[1];
	p->y[2][3] = y[1];
	p->x[3][3] = x[2];
	p->y[3][3] = y[2];
	p->x[3][2] = x[3];
	p->y[3][2] = y[3];
	p->x[3][1] = x[4];
	p->y[3][1] = y[4];
	p->x[3][0] = x[5];
	p->y[3][0] = y[5];
	p->x[2][0] = x[6];
	p->y[2][0] = y[6];
	p->x[1][0] = x[7];
	p->y[1][0] = y[7];
	p->x[1][1] = x[8];
	p->y[1][1] = y[8];
	p->x[1][2] = x[9];
	p->y[1][2] = y[9];
	p->x[2][2] = x[10];
	p->y[2][2] = y[10];
	p->x[2][1] = x[11];
	p->y[2][1] = y[11];
	for (j = 0; j < nComps; ++j) {
	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j];
	  p->color[1][1].c[j] = c[0][j];
	  p->color[1][0].c[j] = c[1][j];
	}
	break;
      }
    }
    ++nPatchesA;
    bitBuf->flushBits();
  }

  if (typeA == 6) {
    for (i = 0; i < nPatchesA; ++i) {
      p = &patchesA[i];
      p->x[1][1] = (-4 * p->x[0][0]
		    +6 * (p->x[0][1] + p->x[1][0])
		    -2 * (p->x[0][3] + p->x[3][0])
		    +3 * (p->x[3][1] + p->x[1][3])
		    - p->x[3][3]) / 9;
      p->y[1][1] = (-4 * p->y[0][0]
		    +6 * (p->y[0][1] + p->y[1][0])
		    -2 * (p->y[0][3] + p->y[3][0])
		    +3 * (p->y[3][1] + p->y[1][3])
		    - p->y[3][3]) / 9;
      p->x[1][2] = (-4 * p->x[0][3]
		    +6 * (p->x[0][2] + p->x[1][3])
		    -2 * (p->x[0][0] + p->x[3][3])
		    +3 * (p->x[3][2] + p->x[1][0])
		    - p->x[3][0]) / 9;
      p->y[1][2] = (-4 * p->y[0][3]
		    +6 * (p->y[0][2] + p->y[1][3])
		    -2 * (p->y[0][0] + p->y[3][3])
		    +3 * (p->y[3][2] + p->y[1][0])
		    - p->y[3][0]) / 9;
      p->x[2][1] = (-4 * p->x[3][0]
		    +6 * (p->x[3][1] + p->x[2][0])
		    -2 * (p->x[3][3] + p->x[0][0])
		    +3 * (p->x[0][1] + p->x[2][3])
		    - p->x[0][3]) / 9;
      p->y[2][1] = (-4 * p->y[3][0]
		    +6 * (p->y[3][1] + p->y[2][0])
		    -2 * (p->y[3][3] + p->y[0][0])
		    +3 * (p->y[0][1] + p->y[2][3])
		    - p->y[0][3]) / 9;
      p->x[2][2] = (-4 * p->x[3][3]
		    +6 * (p->x[3][2] + p->x[2][3])
		    -2 * (p->x[3][0] + p->x[0][3])
		    +3 * (p->x[0][2] + p->x[2][0])
		    - p->x[0][0]) / 9;
      p->y[2][2] = (-4 * p->y[3][3]
		    +6 * (p->y[3][2] + p->y[2][3])
		    -2 * (p->y[3][0] + p->y[0][3])
		    +3 * (p->y[0][2] + p->y[2][0])
		    - p->y[0][0]) / 9;
    }
  }

  shading = new GfxPatchMeshShading(typeA, patchesA, nPatchesA,
				    funcsA, nFuncsA);
  if (!shading->init(res, dict, out, state)) {
    delete shading;
    return nullptr;
  }
  return shading;
}

void GfxPatchMeshShading::getParameterizedColor(double t, GfxColor *color) const {
  double out[gfxColorMaxComps] = {};

  for (int j = 0; j < nFuncs; ++j) {
    funcs[j]->transform(&t, &out[j]);
  }
  for (int j = 0; j < gfxColorMaxComps; ++j) {
    color->c[j] = dblToCol(out[j]);
  }
}

GfxShading *GfxPatchMeshShading::copy() const {
  return new GfxPatchMeshShading(this);
}

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

GfxImageColorMap::GfxImageColorMap(int bitsA, Object *decode,
				   GfxColorSpace *colorSpaceA) {
  GfxIndexedColorSpace *indexedCS;
  GfxSeparationColorSpace *sepCS;
  int maxPixel, indexHigh;
  unsigned char *indexedLookup;
  const Function *sepFunc;
  double x[gfxColorMaxComps];
  double y[gfxColorMaxComps] = {};
  int i, j, k;
  double mapped;
  bool useByteLookup;

  ok = true;
  useMatte = false;

  colorSpace = colorSpaceA;

  // initialize
  for (k = 0; k < gfxColorMaxComps; ++k) {
    lookup[k] = nullptr;
    lookup2[k] = nullptr;
  }
  byte_lookup = nullptr;

  // bits per component and color space
  if (unlikely(bitsA <= 0 || bitsA > 30))
    goto err1;

  bits = bitsA;
  maxPixel = (1 << bits) - 1;

  // this is a hack to support 16 bits images, everywhere
  // we assume a component fits in 8 bits, with this hack
  // we treat 16 bit images as 8 bit ones until it's fixed correctly.
  // The hack has another part on ImageStream::getLine
  if (maxPixel > 255) maxPixel = 255;

  // get decode map
  if (decode->isNull()) {
    nComps = colorSpace->getNComps();
    colorSpace->getDefaultRanges(decodeLow, decodeRange, maxPixel);
  } else if (decode->isArray()) {
    nComps = decode->arrayGetLength() / 2;
    if (nComps < colorSpace->getNComps()) {
      goto err1;
    }
    if (nComps > colorSpace->getNComps()) {
      error(errSyntaxWarning, -1, "Too many elements in Decode array");
      nComps = colorSpace->getNComps();
    }
    for (i = 0; i < nComps; ++i) {
      Object obj = decode->arrayGet(2*i);
      if (!obj.isNum()) {
	goto err1;
      }
      decodeLow[i] = obj.getNum();
      obj = decode->arrayGet(2*i+1);
      if (!obj.isNum()) {
	goto err1;
      }
      decodeRange[i] = obj.getNum() - decodeLow[i];
    }
  } else {
    goto err1;
  }

  // Construct a lookup table -- this stores pre-computed decoded
  // values for each component, i.e., the result of applying the
  // decode mapping to each possible image pixel component value.
  for (k = 0; k < nComps; ++k) {
    lookup[k] = (GfxColorComp *)gmallocn(maxPixel + 1,
					 sizeof(GfxColorComp));
    for (i = 0; i <= maxPixel; ++i) {
      lookup[k][i] = dblToCol(decodeLow[k] +
			      (i * decodeRange[k]) / maxPixel);
    }
  }

  // Optimization: for Indexed and Separation color spaces (which have
  // only one component), we pre-compute a second lookup table with
  // color values
  colorSpace2 = nullptr;
  nComps2 = 0;
  useByteLookup = false;
  switch (colorSpace->getMode()) {
  case csIndexed:
    // Note that indexHigh may not be the same as maxPixel --
    // Distiller will remove unused palette entries, resulting in
    // indexHigh < maxPixel.
    indexedCS = (GfxIndexedColorSpace *)colorSpace;
    colorSpace2 = indexedCS->getBase();
    indexHigh = indexedCS->getIndexHigh();
    nComps2 = colorSpace2->getNComps();
    indexedLookup = indexedCS->getLookup();
    colorSpace2->getDefaultRanges(x, y, indexHigh);
    if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) {
      byte_lookup = (unsigned char *)gmallocn ((maxPixel + 1), nComps2);
      useByteLookup = true;
    }
    for (k = 0; k < nComps2; ++k) {
      lookup2[k] = (GfxColorComp *)gmallocn(maxPixel + 1,
					   sizeof(GfxColorComp));
      for (i = 0; i <= maxPixel; ++i) {
	j = (int)(decodeLow[0] + (i * decodeRange[0]) / maxPixel + 0.5);
	if (j < 0) {
	  j = 0;
	} else if (j > indexHigh) {
	  j = indexHigh;
	}

	mapped = x[k] + (indexedLookup[j*nComps2 + k] / 255.0) * y[k];
	lookup2[k][i] = dblToCol(mapped);
	if (useByteLookup)
	  byte_lookup[i * nComps2 + k] = (unsigned char) (mapped * 255);
      }
    }
    break;
  case csSeparation:
    sepCS = (GfxSeparationColorSpace *)colorSpace;
    colorSpace2 = sepCS->getAlt();
    nComps2 = colorSpace2->getNComps();
    sepFunc = sepCS->getFunc();
    if (colorSpace2->useGetGrayLine() || colorSpace2->useGetRGBLine() || colorSpace2->useGetCMYKLine() || colorSpace2->useGetDeviceNLine()) {
      byte_lookup = (unsigned char *)gmallocn ((maxPixel + 1), nComps2);
      useByteLookup = true;
    }
    for (k = 0; k < nComps2; ++k) {
      lookup2[k] = (GfxColorComp *)gmallocn(maxPixel + 1,
					   sizeof(GfxColorComp));
      for (i = 0; i <= maxPixel; ++i) {
	x[0] = decodeLow[0] + (i * decodeRange[0]) / maxPixel;
	sepFunc->transform(x, y);
	lookup2[k][i] = dblToCol(y[k]);
	if (useByteLookup)
	  byte_lookup[i*nComps2 + k] = (unsigned char) (y[k] * 255);
      }
    }
    break;
  default:
    if (colorSpace->useGetGrayLine() || colorSpace->useGetRGBLine() || colorSpace->useGetCMYKLine() || colorSpace->useGetDeviceNLine()) {
      byte_lookup = (unsigned char *)gmallocn ((maxPixel + 1), nComps);
      useByteLookup = true;
    }
    for (k = 0; k < nComps; ++k) {
      lookup2[k] = (GfxColorComp *)gmallocn(maxPixel + 1,
					   sizeof(GfxColorComp));
      for (i = 0; i <= maxPixel; ++i) {
	mapped = decodeLow[k] + (i * decodeRange[k]) / maxPixel;
	lookup2[k][i] = dblToCol(mapped);
	if (useByteLookup) {
	  int byte;

	  byte = (int) (mapped * 255.0 + 0.5);
	  if (byte < 0)
	    byte = 0;
	  else if (byte > 255)
	    byte = 255;
	  byte_lookup[i * nComps + k] = byte;
	}
      }
    }
  }

  return;

 err1:
  ok = false;
}

GfxImageColorMap::GfxImageColorMap(const GfxImageColorMap *colorMap) {
  int n, i, k;

  colorSpace = colorMap->colorSpace->copy();
  bits = colorMap->bits;
  nComps = colorMap->nComps;
  nComps2 = colorMap->nComps2;
  useMatte = colorMap->useMatte;
  matteColor = colorMap->matteColor;
  colorSpace2 = nullptr;
  for (k = 0; k < gfxColorMaxComps; ++k) {
    lookup[k] = nullptr;
    lookup2[k] = nullptr;
  }
  byte_lookup = nullptr;
  n = 1 << bits;
  for (k = 0; k < nComps; ++k) {
    lookup[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp));
    memcpy(lookup[k], colorMap->lookup[k], n * sizeof(GfxColorComp));
  }
  if (colorSpace->getMode() == csIndexed) {
    colorSpace2 = ((GfxIndexedColorSpace *)colorSpace)->getBase();
    for (k = 0; k < nComps2; ++k) {
      lookup2[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp));
      memcpy(lookup2[k], colorMap->lookup2[k], n * sizeof(GfxColorComp));
    }
  } else if (colorSpace->getMode() == csSeparation) {
    colorSpace2 = ((GfxSeparationColorSpace *)colorSpace)->getAlt();
    for (k = 0; k < nComps2; ++k) {
      lookup2[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp));
      memcpy(lookup2[k], colorMap->lookup2[k], n * sizeof(GfxColorComp));
    }
  } else {
    for (k = 0; k < nComps; ++k) {
      lookup2[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp));
      memcpy(lookup2[k], colorMap->lookup2[k], n * sizeof(GfxColorComp));
    }
  }
  if (colorMap->byte_lookup) {
    int nc = colorSpace2 ? nComps2 : nComps;

    byte_lookup = (unsigned char *)gmallocn (n, nc);
    memcpy(byte_lookup, colorMap->byte_lookup, n * nc);
  }
  for (i = 0; i < nComps; ++i) {
    decodeLow[i] = colorMap->decodeLow[i];
    decodeRange[i] = colorMap->decodeRange[i];
  }
  ok = true;
}

GfxImageColorMap::~GfxImageColorMap() {
  int i;

  delete colorSpace;
  for (i = 0; i < gfxColorMaxComps; ++i) {
    gfree(lookup[i]);
    gfree(lookup2[i]);
  }
  gfree(byte_lookup);
}

void GfxImageColorMap::getGray(const unsigned char *x, GfxGray *gray) {
  GfxColor color;
  int i;

  if (colorSpace2) {
    for (i = 0; i < nComps2; ++i) {
      color.c[i] = lookup2[i][x[0]];
    }
    colorSpace2->getGray(&color, gray);
  } else {
    for (i = 0; i < nComps; ++i) {
      color.c[i] = lookup2[i][x[i]];
    }
    colorSpace->getGray(&color, gray);
  }
}

void GfxImageColorMap::getRGB(const unsigned char *x, GfxRGB *rgb) {
  GfxColor color;
  int i;

  if (colorSpace2) {
    for (i = 0; i < nComps2; ++i) {
      color.c[i] = lookup2[i][x[0]];
    }
    colorSpace2->getRGB(&color, rgb);
  } else {
    for (i = 0; i < nComps; ++i) {
      color.c[i] = lookup2[i][x[i]];
    }
    colorSpace->getRGB(&color, rgb);
  }
}

void GfxImageColorMap::getGrayLine(unsigned char *in, unsigned char *out, int length) {
  int i, j;
  unsigned char *inp, *tmp_line;

  if ((colorSpace2 && !colorSpace2->useGetGrayLine ()) ||
      (!colorSpace2 && !colorSpace->useGetGrayLine ())) {
    GfxGray gray;

    inp = in;
    for (i = 0; i < length; i++) {
      getGray (inp, &gray);
      out[i] = colToByte(gray);
      inp += nComps;
    }
    return;
  }

  switch (colorSpace->getMode()) {
  case csIndexed:
  case csSeparation:
    tmp_line = (unsigned char *) gmallocn (length, nComps2);
    for (i = 0; i < length; i++) {
      for (j = 0; j < nComps2; j++) {
	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
      }
    }
    colorSpace2->getGrayLine(tmp_line, out, length);
    gfree (tmp_line);
    break;

  default:
    inp = in;
    for (j = 0; j < length; j++)
      for (i = 0; i < nComps; i++) {
	*inp = byte_lookup[*inp * nComps + i];
	inp++;
      }
    colorSpace->getGrayLine(in, out, length);
    break;
  }

}

void GfxImageColorMap::getRGBLine(unsigned char *in, unsigned int *out, int length) {
  int i, j;
  unsigned char *inp, *tmp_line;

  if (!useRGBLine()) {
    GfxRGB rgb;

    inp = in;
    for (i = 0; i < length; i++) {
      getRGB (inp, &rgb);
      out[i] =
          ((int) colToByte(rgb.r) << 16) |
          ((int) colToByte(rgb.g) << 8) |
	  ((int) colToByte(rgb.b) << 0);
      inp += nComps;
    }
    return;
  }

  switch (colorSpace->getMode()) {
  case csIndexed:
  case csSeparation:
    tmp_line = (unsigned char *) gmallocn (length, nComps2);
    for (i = 0; i < length; i++) {
      for (j = 0; j < nComps2; j++) {
	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
      }
    }
    colorSpace2->getRGBLine(tmp_line, out, length);
    gfree (tmp_line);
    break;

  default:
    inp = in;
    for (j = 0; j < length; j++)
      for (i = 0; i < nComps; i++) {
	*inp = byte_lookup[*inp * nComps + i];
	inp++;
      }
    colorSpace->getRGBLine(in, out, length);
    break;
  }

}

void GfxImageColorMap::getRGBLine(unsigned char *in, unsigned char *out, int length) {
  int i, j;
  unsigned char *inp, *tmp_line;

  if (!useRGBLine()) {
    GfxRGB rgb;

    inp = in;
    for (i = 0; i < length; i++) {
      getRGB (inp, &rgb);
      *out++ = colToByte(rgb.r);
      *out++ = colToByte(rgb.g);
      *out++ = colToByte(rgb.b);
      inp += nComps;
    }
    return;
  }

  switch (colorSpace->getMode()) {
  case csIndexed:
  case csSeparation:
    tmp_line = (unsigned char *) gmallocn (length, nComps2);
    for (i = 0; i < length; i++) {
      for (j = 0; j < nComps2; j++) {
	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
      }
    }
    colorSpace2->getRGBLine(tmp_line, out, length);
    gfree (tmp_line);
    break;

  default:
    inp = in;
    for (j = 0; j < length; j++)
      for (i = 0; i < nComps; i++) {
	*inp = byte_lookup[*inp * nComps + i];
	inp++;
      }
    colorSpace->getRGBLine(in, out, length);
    break;
  }

}

void GfxImageColorMap::getRGBXLine(unsigned char *in, unsigned char *out, int length) {
  int i, j;
  unsigned char *inp, *tmp_line;

  if (!useRGBLine()) {
    GfxRGB rgb;

    inp = in;
    for (i = 0; i < length; i++) {
      getRGB (inp, &rgb);
      *out++ = colToByte(rgb.r);
      *out++ = colToByte(rgb.g);
      *out++ = colToByte(rgb.b);
      *out++ = 255;
      inp += nComps;
    }
    return;
  }

  switch (colorSpace->getMode()) {
  case csIndexed:
  case csSeparation:
    tmp_line = (unsigned char *) gmallocn (length, nComps2);
    for (i = 0; i < length; i++) {
      for (j = 0; j < nComps2; j++) {
	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
      }
    }
    colorSpace2->getRGBXLine(tmp_line, out, length);
    gfree (tmp_line);
    break;

  default:
    inp = in;
    for (j = 0; j < length; j++)
      for (i = 0; i < nComps; i++) {
	*inp = byte_lookup[*inp * nComps + i];
	inp++;
      }
    colorSpace->getRGBXLine(in, out, length);
    break;
  }

}

void GfxImageColorMap::getCMYKLine(unsigned char *in, unsigned char *out, int length) {
  int i, j;
  unsigned char *inp, *tmp_line;

  if (!useCMYKLine()) {
    GfxCMYK cmyk;

    inp = in;
    for (i = 0; i < length; i++) {
      getCMYK (inp, &cmyk);
      *out++ = colToByte(cmyk.c);
      *out++ = colToByte(cmyk.m);
      *out++ = colToByte(cmyk.y);
      *out++ = colToByte(cmyk.k);
      inp += nComps;
    }
    return;
  }

  switch (colorSpace->getMode()) {
  case csIndexed:
  case csSeparation:
    tmp_line = (unsigned char *) gmallocn (length, nComps2);
    for (i = 0; i < length; i++) {
      for (j = 0; j < nComps2; j++) {
	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
      }
    }
    colorSpace2->getCMYKLine(tmp_line, out, length);
    gfree (tmp_line);
    break;

  default:
    inp = in;
    for (j = 0; j < length; j++)
      for (i = 0; i < nComps; i++) {
	*inp = byte_lookup[*inp * nComps + i];
	inp++;
      }
    colorSpace->getCMYKLine(in, out, length);
    break;
  }

}

void GfxImageColorMap::getDeviceNLine(unsigned char *in, unsigned char *out, int length) {
  unsigned char *inp, *tmp_line;

  if (!useDeviceNLine()) {
    GfxColor deviceN;

    inp = in;
    for (int i = 0; i < length; i++) {
      getDeviceN (inp, &deviceN);
      for (int j = 0; j < SPOT_NCOMPS+4; j++)
        *out++ = deviceN.c[j];
      inp += nComps;
    }
    return;
  }

  switch (colorSpace->getMode()) {
  case csIndexed:
  case csSeparation:
    tmp_line = (unsigned char *) gmallocn (length, nComps2);
    for (int i = 0; i < length; i++) {
      for (int j = 0; j < nComps2; j++) {
	tmp_line[i * nComps2 + j] = byte_lookup[in[i] * nComps2 + j];
      }
    }
    colorSpace2->getDeviceNLine(tmp_line, out, length);
    gfree (tmp_line);
    break;

  default:
    inp = in;
    for (int j = 0; j < length; j++)
      for (int i = 0; i < nComps; i++) {
	*inp = byte_lookup[*inp * nComps + i];
	inp++;
      }
    colorSpace->getDeviceNLine(in, out, length);
    break;
  }

}

void GfxImageColorMap::getCMYK(const unsigned char *x, GfxCMYK *cmyk) {
  GfxColor color;
  int i;

  if (colorSpace2) {
    for (i = 0; i < nComps2; ++i) {
      color.c[i] = lookup2[i][x[0]];
    }
    colorSpace2->getCMYK(&color, cmyk);
  } else {
    for (i = 0; i < nComps; ++i) {
      color.c[i] = lookup[i][x[i]];
    }
    colorSpace->getCMYK(&color, cmyk);
  }
}

void GfxImageColorMap::getDeviceN(const unsigned char *x, GfxColor *deviceN) {
  GfxColor color;
  int i;

  if (colorSpace2) {
    for (i = 0; i < nComps2; ++i) {
      color.c[i] = lookup2[i][x[0]];
    }
    colorSpace2->getDeviceN(&color, deviceN);
  } else {
    for (i = 0; i < nComps; ++i) {
      color.c[i] = lookup[i][x[i]];
    }
    colorSpace->getDeviceN(&color, deviceN);
  }
}

void GfxImageColorMap::getColor(const unsigned char *x, GfxColor *color) {
  int maxPixel, i;

  maxPixel = (1 << bits) - 1;
  for (i = 0; i < nComps; ++i) {
    color->c[i] = dblToCol(decodeLow[i] + (x[i] * decodeRange[i]) / maxPixel);
  }
}

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

GfxSubpath::GfxSubpath(double x1, double y1) {
  size = 16;
  x = (double *)gmallocn(size, sizeof(double));
  y = (double *)gmallocn(size, sizeof(double));
  curve = (bool *)gmallocn(size, sizeof(bool));
  n = 1;
  x[0] = x1;
  y[0] = y1;
  curve[0] = false;
  closed = false;
}

GfxSubpath::~GfxSubpath() {
  gfree(x);
  gfree(y);
  gfree(curve);
}

// Used for copy().
GfxSubpath::GfxSubpath(const GfxSubpath *subpath) {
  size = subpath->size;
  n = subpath->n;
  x = (double *)gmallocn(size, sizeof(double));
  y = (double *)gmallocn(size, sizeof(double));
  curve = (bool *)gmallocn(size, sizeof(bool));
  memcpy(x, subpath->x, n * sizeof(double));
  memcpy(y, subpath->y, n * sizeof(double));
  memcpy(curve, subpath->curve, n * sizeof(bool));
  closed = subpath->closed;
}

void GfxSubpath::lineTo(double x1, double y1) {
  if (n >= size) {
    size *= 2;
    x = (double *)greallocn(x, size, sizeof(double));
    y = (double *)greallocn(y, size, sizeof(double));
    curve = (bool *)greallocn(curve, size, sizeof(bool));
  }
  x[n] = x1;
  y[n] = y1;
  curve[n] = false;
  ++n;
}

void GfxSubpath::curveTo(double x1, double y1, double x2, double y2,
			 double x3, double y3) {
  if (n+3 > size) {
    size *= 2;
    x = (double *)greallocn(x, size, sizeof(double));
    y = (double *)greallocn(y, size, sizeof(double));
    curve = (bool *)greallocn(curve, size, sizeof(bool));
  }
  x[n] = x1;
  y[n] = y1;
  x[n+1] = x2;
  y[n+1] = y2;
  x[n+2] = x3;
  y[n+2] = y3;
  curve[n] = curve[n+1] = true;
  curve[n+2] = false;
  n += 3;
}

void GfxSubpath::close() {
  if (x[n-1] != x[0] || y[n-1] != y[0]) {
    lineTo(x[0], y[0]);
  }
  closed = true;
}

void GfxSubpath::offset(double dx, double dy) {
  int i;

  for (i = 0; i < n; ++i) {
    x[i] += dx;
    y[i] += dy;
  }
}

GfxPath::GfxPath() {
  justMoved = false;
  size = 16;
  n = 0;
  firstX = firstY = 0;
  subpaths = (GfxSubpath **)gmallocn(size, sizeof(GfxSubpath *));
}

GfxPath::~GfxPath() {
  int i;

  for (i = 0; i < n; ++i)
    delete subpaths[i];
  gfree(subpaths);
}

// Used for copy().
GfxPath::GfxPath(bool justMoved1, double firstX1, double firstY1,
		 GfxSubpath **subpaths1, int n1, int size1) {
  int i;

  justMoved = justMoved1;
  firstX = firstX1;
  firstY = firstY1;
  size = size1;
  n = n1;
  subpaths = (GfxSubpath **)gmallocn(size, sizeof(GfxSubpath *));
  for (i = 0; i < n; ++i)
    subpaths[i] = subpaths1[i]->copy();
}

void GfxPath::moveTo(double x, double y) {
  justMoved = true;
  firstX = x;
  firstY = y;
}

void GfxPath::lineTo(double x, double y) {
  if (justMoved || (n > 0 && subpaths[n-1]->isClosed())) {
    if (n >= size) {
      size *= 2;
      subpaths = (GfxSubpath **)
	           greallocn(subpaths, size, sizeof(GfxSubpath *));
    }
    if (justMoved) {
      subpaths[n] = new GfxSubpath(firstX, firstY);
    } else {
      subpaths[n] = new GfxSubpath(subpaths[n-1]->getLastX(),
				   subpaths[n-1]->getLastY());
    }
    ++n;
    justMoved = false;
  }
  subpaths[n-1]->lineTo(x, y);
}

void GfxPath::curveTo(double x1, double y1, double x2, double y2,
	     double x3, double y3) {
  if (justMoved || (n > 0 && subpaths[n-1]->isClosed())) {
    if (n >= size) {
      size *= 2;
      subpaths = (GfxSubpath **) 
 	         greallocn(subpaths, size, sizeof(GfxSubpath *));
    }
    if (justMoved) {
      subpaths[n] = new GfxSubpath(firstX, firstY);
    } else {
      subpaths[n] = new GfxSubpath(subpaths[n-1]->getLastX(),
				   subpaths[n-1]->getLastY());
    }
    ++n;
    justMoved = false;
  }
  subpaths[n-1]->curveTo(x1, y1, x2, y2, x3, y3);
}

void GfxPath::close() {
  // this is necessary to handle the pathological case of
  // moveto/closepath/clip, which defines an empty clipping region
  if (justMoved) {
    if (n >= size) {
      size *= 2;
      subpaths = (GfxSubpath **)
	greallocn(subpaths, size, sizeof(GfxSubpath *));
    }
    subpaths[n] = new GfxSubpath(firstX, firstY);
    ++n;
    justMoved = false;
  }
  subpaths[n-1]->close();
}

void GfxPath::append(GfxPath *path) {
  int i;

  if (n + path->n > size) {
    size = n + path->n;
    subpaths = (GfxSubpath **)
                 greallocn(subpaths, size, sizeof(GfxSubpath *));
  }
  for (i = 0; i < path->n; ++i) {
    subpaths[n++] = path->subpaths[i]->copy();
  }
  justMoved = false;
}

void GfxPath::offset(double dx, double dy) {
  int i;

  for (i = 0; i < n; ++i) {
    subpaths[i]->offset(dx, dy);
  }
}

//------------------------------------------------------------------------
//
//------------------------------------------------------------------------
GfxState::ReusablePathIterator::ReusablePathIterator(GfxPath *pathA)
 : path(pathA),
   subPathOff(0),
   coordOff(0),
   numCoords(0),
   curSubPath(nullptr)
{
  if( path->getNumSubpaths() ) {
    curSubPath = path->getSubpath(subPathOff);
    numCoords = curSubPath->getNumPoints();
  }
}

bool GfxState::ReusablePathIterator::isEnd() const {
   return coordOff >= numCoords;
}

void GfxState::ReusablePathIterator::next() {
  ++coordOff;
  if (coordOff == numCoords) {
    ++subPathOff;
    if (subPathOff < path->getNumSubpaths()) {
      coordOff = 0;
      curSubPath = path->getSubpath(subPathOff);
      numCoords = curSubPath->getNumPoints();
    }
  }
}

void GfxState::ReusablePathIterator::setCoord(double x, double y) {
  curSubPath->setX(coordOff, x);
  curSubPath->setY(coordOff, y);
}

void GfxState::ReusablePathIterator::reset() {
  coordOff = 0;
  subPathOff = 0;
  curSubPath = path->getSubpath(0);
  numCoords = curSubPath->getNumPoints();
}

GfxState::GfxState(double hDPIA, double vDPIA, const PDFRectangle *pageBox,
		   int rotateA, bool upsideDown) {
  double kx, ky;

  hDPI = hDPIA;
  vDPI = vDPIA;
  rotate = rotateA;
  px1 = pageBox->x1;
  py1 = pageBox->y1;
  px2 = pageBox->x2;
  py2 = pageBox->y2;
  kx = hDPI / 72.0;
  ky = vDPI / 72.0;
  if (rotate == 90) {
    ctm[0] = 0;
    ctm[1] = upsideDown ? ky : -ky;
    ctm[2] = kx;
    ctm[3] = 0;
    ctm[4] = -kx * py1;
    ctm[5] = ky * (upsideDown ? -px1 : px2);
    pageWidth = kx * (py2 - py1);
    pageHeight = ky * (px2 - px1);
  } else if (rotate == 180) {
    ctm[0] = -kx;
    ctm[1] = 0;
    ctm[2] = 0;
    ctm[3] = upsideDown ? ky : -ky;
    ctm[4] = kx * px2;
    ctm[5] = ky * (upsideDown ? -py1 : py2);
    pageWidth = kx * (px2 - px1);
    pageHeight = ky * (py2 - py1);
  } else if (rotate == 270) {
    ctm[0] = 0;
    ctm[1] = upsideDown ? -ky : ky;
    ctm[2] = -kx;
    ctm[3] = 0;
    ctm[4] = kx * py2;
    ctm[5] = ky * (upsideDown ? px2 : -px1);
    pageWidth = kx * (py2 - py1);
    pageHeight = ky * (px2 - px1);
  } else {
    ctm[0] = kx;
    ctm[1] = 0;
    ctm[2] = 0;
    ctm[3] = upsideDown ? -ky : ky;
    ctm[4] = -kx * px1;
    ctm[5] = ky * (upsideDown ? py2 : -py1);
    pageWidth = kx * (px2 - px1);
    pageHeight = ky * (py2 - py1);
  }

  fillColorSpace = new GfxDeviceGrayColorSpace();
  strokeColorSpace = new GfxDeviceGrayColorSpace();
  fillColor.c[0] = 0;
  strokeColor.c[0] = 0;
  fillPattern = nullptr;
  strokePattern = nullptr;
  blendMode = gfxBlendNormal;
  fillOpacity = 1;
  strokeOpacity = 1;
  fillOverprint = false;
  strokeOverprint = false;
  overprintMode = 0;
  transfer[0] = transfer[1] = transfer[2] = transfer[3] = nullptr;

  lineWidth = 1;
  lineDash = nullptr;
  lineDashLength = 0;
  lineDashStart = 0;
  flatness = 1;
  lineJoin = 0;
  lineCap = 0;
  miterLimit = 10;
  strokeAdjust = false;
  alphaIsShape = false;
  textKnockout = false;

  font = nullptr;
  fontSize = 0;
  textMat[0] = 1; textMat[1] = 0;
  textMat[2] = 0; textMat[3] = 1;
  textMat[4] = 0; textMat[5] = 0;
  charSpace = 0;
  wordSpace = 0;
  horizScaling = 1;
  leading = 0;
  rise = 0;
  render = 0;

  path = new GfxPath();
  curX = curY = 0;
  lineX = lineY = 0;

  clipXMin = 0;
  clipYMin = 0;
  clipXMax = pageWidth;
  clipYMax = pageHeight;

  renderingIntent[0] = 0;

  saved = nullptr;
#ifdef USE_CMS
  GfxColorSpace::setupColorProfiles();
  XYZ2DisplayTransformRelCol = nullptr;
  XYZ2DisplayTransformAbsCol = nullptr;
  XYZ2DisplayTransformSat = nullptr;
  XYZ2DisplayTransformPerc = nullptr;
  localDisplayProfile = nullptr;
  displayProfileRef = 0;
#endif
}

GfxState::~GfxState() {
  int i;

  if (fillColorSpace) {
    delete fillColorSpace;
  }
  if (strokeColorSpace) {
    delete strokeColorSpace;
  }
  if (fillPattern) {
    delete fillPattern;
  }
  if (strokePattern) {
    delete strokePattern;
  }
  for (i = 0; i < 4; ++i) {
    if (transfer[i]) {
      delete transfer[i];
    }
  }
  gfree(lineDash);
  if (path) {
    // this gets set to NULL by restore()
    delete path;
  }
  if (font) {
    font->decRefCnt();
  }
#ifdef USE_CMS
  if (XYZ2DisplayTransformRelCol) {
    if (XYZ2DisplayTransformRelCol->unref() == 0)
      delete XYZ2DisplayTransformRelCol;
  }
  if (XYZ2DisplayTransformAbsCol) {
    if (XYZ2DisplayTransformAbsCol->unref() == 0)
      delete XYZ2DisplayTransformAbsCol;
  }
  if (XYZ2DisplayTransformSat) {
    if (XYZ2DisplayTransformSat->unref() == 0)
      delete XYZ2DisplayTransformSat;
  }
  if (XYZ2DisplayTransformPerc) {
    if (XYZ2DisplayTransformPerc->unref() == 0)
      delete XYZ2DisplayTransformPerc;
  }
  if (--displayProfileRef == 0 && localDisplayProfile != nullptr) {
    cmsCloseProfile(localDisplayProfile);
  }
#endif
}

// Used for copy();
GfxState::GfxState(const GfxState *state, bool copyPath) {
  int i;

  memcpy(this, state, sizeof(GfxState));
  if (fillColorSpace) {
    fillColorSpace = state->fillColorSpace->copy();
  }
  if (strokeColorSpace) {
    strokeColorSpace = state->strokeColorSpace->copy();
  }
  if (fillPattern) {
    fillPattern = state->fillPattern->copy();
  }
  if (strokePattern) {
    strokePattern = state->strokePattern->copy();
  }
  for (i = 0; i < 4; ++i) {
    if (transfer[i]) {
      transfer[i] = state->transfer[i]->copy();
    }
  }
  if (lineDashLength > 0) {
    lineDash = (double *)gmallocn(lineDashLength, sizeof(double));
    memcpy(lineDash, state->lineDash, lineDashLength * sizeof(double));
  }
  if (font)
    font->incRefCnt();

  if (copyPath) {
    path = state->path->copy();
  }
  saved = nullptr;
#ifdef USE_CMS
  if (XYZ2DisplayTransformRelCol) {
    XYZ2DisplayTransformRelCol->ref();
  }
  if (XYZ2DisplayTransformAbsCol) {
    XYZ2DisplayTransformAbsCol->ref();
  }
  if (XYZ2DisplayTransformSat) {
    XYZ2DisplayTransformSat->ref();
  }
  if (XYZ2DisplayTransformPerc) {
    XYZ2DisplayTransformPerc->ref();
  }
  if (localDisplayProfile) {
    displayProfileRef++;
  }
#endif
}

#ifdef USE_CMS
void GfxState::setDisplayProfile(cmsHPROFILE localDisplayProfileA) {
  if (localDisplayProfile != nullptr) {
    cmsCloseProfile(localDisplayProfile);
  }
  localDisplayProfile = localDisplayProfileA;
  if (localDisplayProfileA != nullptr) {
    cmsHTRANSFORM transform;
    unsigned int nChannels;
    unsigned int localDisplayPixelType;

    localDisplayPixelType = getCMSColorSpaceType(cmsGetColorSpace(localDisplayProfile));
    nChannels = getCMSNChannels(cmsGetColorSpace(localDisplayProfile));
    displayProfileRef = 1;
    // create transform from XYZ
    cmsHPROFILE XYZProfile = cmsCreateXYZProfile();
    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
	   localDisplayProfile,
	   COLORSPACE_SH(localDisplayPixelType) |
	     CHANNELS_SH(nChannels) | BYTES_SH(1),
	  INTENT_RELATIVE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
      error(errSyntaxWarning, -1, "Can't create Lab transform");
    } else {
      XYZ2DisplayTransformRelCol = new GfxColorTransform(transform, INTENT_RELATIVE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
    }
    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
	   localDisplayProfile,
	   COLORSPACE_SH(localDisplayPixelType) |
	     CHANNELS_SH(nChannels) | BYTES_SH(1),
	  INTENT_ABSOLUTE_COLORIMETRIC,LCMS_FLAGS)) == nullptr) {
      error(errSyntaxWarning, -1, "Can't create Lab transform");
    } else {
      XYZ2DisplayTransformAbsCol = new GfxColorTransform(transform, INTENT_ABSOLUTE_COLORIMETRIC, PT_XYZ, localDisplayPixelType);
    }
    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
	   localDisplayProfile,
	   COLORSPACE_SH(localDisplayPixelType) |
	     CHANNELS_SH(nChannels) | BYTES_SH(1),
	  INTENT_SATURATION,LCMS_FLAGS)) == nullptr) {
      error(errSyntaxWarning, -1, "Can't create Lab transform");
    } else {
      XYZ2DisplayTransformSat = new GfxColorTransform(transform, INTENT_SATURATION, PT_XYZ, localDisplayPixelType);
    }
    if ((transform = cmsCreateTransform(XYZProfile, TYPE_XYZ_DBL,
	   localDisplayProfile,
	   COLORSPACE_SH(localDisplayPixelType) |
	     CHANNELS_SH(nChannels) | BYTES_SH(1),
	  INTENT_PERCEPTUAL,LCMS_FLAGS)) == nullptr) {
      error(errSyntaxWarning, -1, "Can't create Lab transform");
    } else {
      XYZ2DisplayTransformPerc = new GfxColorTransform(transform, INTENT_PERCEPTUAL, PT_XYZ, localDisplayPixelType);
    }
    cmsCloseProfile(XYZProfile);
  }
}

GfxColorTransform *GfxState::getXYZ2DisplayTransform() {
  GfxColorTransform *transform;

  transform = XYZ2DisplayTransformRelCol;
  if (strcmp(renderingIntent, "AbsoluteColorimetric") == 0) {
    transform = XYZ2DisplayTransformAbsCol;
  } else if (strcmp(renderingIntent, "Saturation") == 0) {
    transform = XYZ2DisplayTransformSat;
  } else if (strcmp(renderingIntent, "Perceptual") == 0) {
    transform = XYZ2DisplayTransformPerc;
  }
  if (transform == nullptr) {
    transform = XYZ2DisplayTransform;
  }
  return transform;
}

#endif

void GfxState::setPath(GfxPath *pathA) {
  delete path;
  path = pathA;
}

void GfxState::getUserClipBBox(double *xMin, double *yMin,
			       double *xMax, double *yMax) const {
  double ictm[6];
  double xMin1, yMin1, xMax1, yMax1, tx, ty;

  // invert the CTM
  const double det_denominator = (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
  if (unlikely(det_denominator == 0)) {
      *xMin = 0;
      *yMin = 0;
      *xMax = 0;
      *yMax = 0;
      return;
  }
  const double det = 1 / det_denominator;
  ictm[0] = ctm[3] * det;
  ictm[1] = -ctm[1] * det;
  ictm[2] = -ctm[2] * det;
  ictm[3] = ctm[0] * det;
  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;

  // transform all four corners of the clip bbox; find the min and max
  // x and y values
  xMin1 = xMax1 = clipXMin * ictm[0] + clipYMin * ictm[2] + ictm[4];
  yMin1 = yMax1 = clipXMin * ictm[1] + clipYMin * ictm[3] + ictm[5];
  tx = clipXMin * ictm[0] + clipYMax * ictm[2] + ictm[4];
  ty = clipXMin * ictm[1] + clipYMax * ictm[3] + ictm[5];
  if (tx < xMin1) {
    xMin1 = tx;
  } else if (tx > xMax1) {
    xMax1 = tx;
  }
  if (ty < yMin1) {
    yMin1 = ty;
  } else if (ty > yMax1) {
    yMax1 = ty;
  }
  tx = clipXMax * ictm[0] + clipYMin * ictm[2] + ictm[4];
  ty = clipXMax * ictm[1] + clipYMin * ictm[3] + ictm[5];
  if (tx < xMin1) {
    xMin1 = tx;
  } else if (tx > xMax1) {
    xMax1 = tx;
  }
  if (ty < yMin1) {
    yMin1 = ty;
  } else if (ty > yMax1) {
    yMax1 = ty;
  }
  tx = clipXMax * ictm[0] + clipYMax * ictm[2] + ictm[4];
  ty = clipXMax * ictm[1] + clipYMax * ictm[3] + ictm[5];
  if (tx < xMin1) {
    xMin1 = tx;
  } else if (tx > xMax1) {
    xMax1 = tx;
  }
  if (ty < yMin1) {
    yMin1 = ty;
  } else if (ty > yMax1) {
    yMax1 = ty;
  }

  *xMin = xMin1;
  *yMin = yMin1;
  *xMax = xMax1;
  *yMax = yMax1;
}

double GfxState::transformWidth(double w) const {
  double x, y;

  x = ctm[0] + ctm[2];
  y = ctm[1] + ctm[3];
  return w * sqrt(0.5 * (x * x + y * y));
}

double GfxState::getTransformedFontSize() const {
  double x1, y1, x2, y2;

  x1 = textMat[2] * fontSize;
  y1 = textMat[3] * fontSize;
  x2 = ctm[0] * x1 + ctm[2] * y1;
  y2 = ctm[1] * x1 + ctm[3] * y1;
  return sqrt(x2 * x2 + y2 * y2);
}

void GfxState::getFontTransMat(double *m11, double *m12,
			       double *m21, double *m22) const {
  *m11 = (textMat[0] * ctm[0] + textMat[1] * ctm[2]) * fontSize;
  *m12 = (textMat[0] * ctm[1] + textMat[1] * ctm[3]) * fontSize;
  *m21 = (textMat[2] * ctm[0] + textMat[3] * ctm[2]) * fontSize;
  *m22 = (textMat[2] * ctm[1] + textMat[3] * ctm[3]) * fontSize;
}

void GfxState::setCTM(double a, double b, double c,
		      double d, double e, double f) {
  ctm[0] = a;
  ctm[1] = b;
  ctm[2] = c;
  ctm[3] = d;
  ctm[4] = e;
  ctm[5] = f;
}

void GfxState::concatCTM(double a, double b, double c,
			 double d, double e, double f) {
  double a1 = ctm[0];
  double b1 = ctm[1];
  double c1 = ctm[2];
  double d1 = ctm[3];

  ctm[0] = a * a1 + b * c1;
  ctm[1] = a * b1 + b * d1;
  ctm[2] = c * a1 + d * c1;
  ctm[3] = c * b1 + d * d1;
  ctm[4] = e * a1 + f * c1 + ctm[4];
  ctm[5] = e * b1 + f * d1 + ctm[5];
}

void GfxState::shiftCTMAndClip(double tx, double ty) {
  ctm[4] += tx;
  ctm[5] += ty;
  clipXMin += tx;
  clipYMin += ty;
  clipXMax += tx;
  clipYMax += ty;
}

void GfxState::setFillColorSpace(GfxColorSpace *colorSpace) {
  if (fillColorSpace) {
    delete fillColorSpace;
  }
  fillColorSpace = colorSpace;
}

void GfxState::setStrokeColorSpace(GfxColorSpace *colorSpace) {
  if (strokeColorSpace) {
    delete strokeColorSpace;
  }
  strokeColorSpace = colorSpace;
}

void GfxState::setFillPattern(GfxPattern *pattern) {
  if (fillPattern) {
    delete fillPattern;
  }
  fillPattern = pattern;
}

void GfxState::setStrokePattern(GfxPattern *pattern) {
  if (strokePattern) {
    delete strokePattern;
  }
  strokePattern = pattern;
}

void GfxState::setFont(GfxFont *fontA, double fontSizeA) {
  if (font)
    font->decRefCnt();

  font = fontA;
  fontSize = fontSizeA;
}

void GfxState::setTransfer(Function **funcs) {
  int i;

  for (i = 0; i < 4; ++i) {
    if (transfer[i]) {
      delete transfer[i];
    }
    transfer[i] = funcs[i];
  }
}

void GfxState::setLineDash(double *dash, int length, double start) {
  if (lineDash)
    gfree(lineDash);
  lineDash = dash;
  lineDashLength = length;
  lineDashStart = start;
}

void GfxState::clearPath() {
  delete path;
  path = new GfxPath();
}

void GfxState::clip() {
  double xMin, yMin, xMax, yMax, x, y;
  GfxSubpath *subpath;
  int i, j;

  xMin = xMax = yMin = yMax = 0; // make gcc happy
  for (i = 0; i < path->getNumSubpaths(); ++i) {
    subpath = path->getSubpath(i);
    for (j = 0; j < subpath->getNumPoints(); ++j) {
      transform(subpath->getX(j), subpath->getY(j), &x, &y);
      if (i == 0 && j == 0) {
	xMin = xMax = x;
	yMin = yMax = y;
      } else {
	if (x < xMin) {
	  xMin = x;
	} else if (x > xMax) {
	  xMax = x;
	}
	if (y < yMin) {
	  yMin = y;
	} else if (y > yMax) {
	  yMax = y;
	}
      }
    }
  }
  if (xMin > clipXMin) {
    clipXMin = xMin;
  }
  if (yMin > clipYMin) {
    clipYMin = yMin;
  }
  if (xMax < clipXMax) {
    clipXMax = xMax;
  }
  if (yMax < clipYMax) {
    clipYMax = yMax;
  }
}

void GfxState::clipToStrokePath() {
  double xMin, yMin, xMax, yMax, x, y, t0, t1;
  GfxSubpath *subpath;
  int i, j;

  xMin = xMax = yMin = yMax = 0; // make gcc happy
  for (i = 0; i < path->getNumSubpaths(); ++i) {
    subpath = path->getSubpath(i);
    for (j = 0; j < subpath->getNumPoints(); ++j) {
      transform(subpath->getX(j), subpath->getY(j), &x, &y);
      if (i == 0 && j == 0) {
	xMin = xMax = x;
	yMin = yMax = y;
      } else {
	if (x < xMin) {
	  xMin = x;
	} else if (x > xMax) {
	  xMax = x;
	}
	if (y < yMin) {
	  yMin = y;
	} else if (y > yMax) {
	  yMax = y;
	}
      }
    }
  }

  // allow for the line width
  //~ miter joins can extend farther than this
  t0 = fabs(ctm[0]);
  t1 = fabs(ctm[2]);
  if (t0 > t1) {
    xMin -= 0.5 * lineWidth * t0;
    xMax += 0.5 * lineWidth * t0;
  } else {
    xMin -= 0.5 * lineWidth * t1;
    xMax += 0.5 * lineWidth * t1;
  }
  t0 = fabs(ctm[0]);
  t1 = fabs(ctm[3]);
  if (t0 > t1) {
    yMin -= 0.5 * lineWidth * t0;
    yMax += 0.5 * lineWidth * t0;
  } else {
    yMin -= 0.5 * lineWidth * t1;
    yMax += 0.5 * lineWidth * t1;
  }

  if (xMin > clipXMin) {
    clipXMin = xMin;
  }
  if (yMin > clipYMin) {
    clipYMin = yMin;
  }
  if (xMax < clipXMax) {
    clipXMax = xMax;
  }
  if (yMax < clipYMax) {
    clipYMax = yMax;
  }
}

void GfxState::clipToRect(double xMin, double yMin, double xMax, double yMax) {
  double x, y, xMin1, yMin1, xMax1, yMax1;

  transform(xMin, yMin, &x, &y);
  xMin1 = xMax1 = x;
  yMin1 = yMax1 = y;
  transform(xMax, yMin, &x, &y);
  if (x < xMin1) {
    xMin1 = x;
  } else if (x > xMax1) {
    xMax1 = x;
  }
  if (y < yMin1) {
    yMin1 = y;
  } else if (y > yMax1) {
    yMax1 = y;
  }
  transform(xMax, yMax, &x, &y);
  if (x < xMin1) {
    xMin1 = x;
  } else if (x > xMax1) {
    xMax1 = x;
  }
  if (y < yMin1) {
    yMin1 = y;
  } else if (y > yMax1) {
    yMax1 = y;
  }
  transform(xMin, yMax, &x, &y);
  if (x < xMin1) {
    xMin1 = x;
  } else if (x > xMax1) {
    xMax1 = x;
  }
  if (y < yMin1) {
    yMin1 = y;
  } else if (y > yMax1) {
    yMax1 = y;
  }

  if (xMin1 > clipXMin) {
    clipXMin = xMin1;
  }
  if (yMin1 > clipYMin) {
    clipYMin = yMin1;
  }
  if (xMax1 < clipXMax) {
    clipXMax = xMax1;
  }
  if (yMax1 < clipYMax) {
    clipYMax = yMax1;
  }
}

void GfxState::textShift(double tx, double ty) {
  double dx, dy;

  textTransformDelta(tx, ty, &dx, &dy);
  curX += dx;
  curY += dy;
}

void GfxState::shift(double dx, double dy) {
  curX += dx;
  curY += dy;
}

GfxState *GfxState::save() {
  GfxState *newState;

  newState = copy();
  newState->saved = this;
  return newState;
}

GfxState *GfxState::restore() {
  GfxState *oldState;

  if (saved) {
    oldState = saved;

    // these attributes aren't saved/restored by the q/Q operators
    oldState->path = path;
    oldState->curX = curX;
    oldState->curY = curY;
    oldState->lineX = lineX;
    oldState->lineY = lineY;

    path = nullptr;
    saved = nullptr;
    delete this;

  } else {
    oldState = this;
  }

  return oldState;
}

bool GfxState::parseBlendMode(Object *obj, GfxBlendMode *mode) {
  int i, j;

  if (obj->isName()) {
    for (i = 0; i < nGfxBlendModeNames; ++i) {
      if (!strcmp(obj->getName(), gfxBlendModeNames[i].name)) {
	*mode = gfxBlendModeNames[i].mode;
	return true;
      }
    }
    return false;
  } else if (obj->isArray()) {
    for (i = 0; i < obj->arrayGetLength(); ++i) {
      Object obj2 = obj->arrayGet(i);
      if (!obj2.isName()) {
	return false;
      }
      for (j = 0; j < nGfxBlendModeNames; ++j) {
	if (!strcmp(obj2.getName(), gfxBlendModeNames[j].name)) {
	  *mode = gfxBlendModeNames[j].mode;
	  return true;
	}
      }
    }
    *mode = gfxBlendNormal;
    return true;
  } else {
    return false;
  }
}
