[poppler] poppler/Function.cc poppler/Function.h poppler/GfxState.cc poppler/GfxState.h poppler/SplashOutputDev.cc poppler/SplashOutputDev.h splash/SplashBitmap.cc splash/SplashBitmap.h splash/Splash.cc splash/Splash.h splash/SplashState.cc splash/SplashState.h splash/SplashTypes.h utils/pdftoppm.cc

Albert Astals Cid aacid at kemper.freedesktop.org
Tue Sep 11 14:34:37 PDT 2012


 poppler/Function.cc        |   14 
 poppler/Function.h         |    4 
 poppler/GfxState.cc        |  341 +++++++++++++++++
 poppler/GfxState.h         |   41 +-
 poppler/SplashOutputDev.cc |  173 ++++++++
 poppler/SplashOutputDev.h  |    3 
 splash/Splash.cc           |  255 ++++++++++++-
 splash/Splash.h            |    7 
 splash/SplashBitmap.cc     |  115 +++++
 splash/SplashBitmap.h      |    8 
 splash/SplashState.cc      |   30 +
 splash/SplashState.h       |    3 
 splash/SplashTypes.h       |   30 +
 utils/pdftoppm.cc          |  878 ++++++++++++++++++++++-----------------------
 14 files changed, 1415 insertions(+), 487 deletions(-)

New commits:
commit 2e77799a1668f949612f551425d0665c59ff1d93
Author: Thomas Freitag <Thomas.Freitag at alfa.de>
Date:   Tue Sep 11 23:33:25 2012 +0200

    Splash: Implement DeviceN support
    
    Bug #53140
    
    Some copying from the bug tracker
    
     To explain
    it a little bit more I copy a few lines from "Patch 8.01 — DeviceN Support (6
    colors)" of the Ghent PDF workgroup:
    "This patch tests the DeviceN capabilities of a workflow. If DeviceN is not
    handled correctly the colors are converted to CMYK. Instead of the check marks
    an X will appear in the lower left corner of each image and in the gradient.
    In addition you could inspect the color separations. The objects should appear
    only in the Black, Pantone 265C and GWG Green separations as indicated in the
    captions."
    Without the patch all DeviceN colors are immediately converted to CMYK (with
    SPLASH_CMYK). This leads especially to problems, if overprint is used: in
    overprint mode a CMYK color will knockout the underlying CMYK components, BUT
    neither any spot colors. But if underlying spot colors are immediately
    converted to CMYK colors, they will be kocked out then, too!
    The patch now spends up to four (or up to SPOT_NCOMPS) additional spot colors
    in the splash bitmap, so the order in the bitmap will be
    CMYKSTUVCMYKSTUVCMYKSTUV... where S, T, U, V are spot colors (I would use
    S1,S2, S3, S4 if it's possible to use indexes), and all painting operations are
    done now in this new device. Only at the end, when we want to store the bitmap
    in a CMYK or RGB color, the spot colors are converted and their alternate CMYK
    components are added to the normal CMYK components.
    According to the PDF spec are PDF writer should use different spot color names
    if they have a different appearance in their alternate CMYK colorspace.
    "hasDifferntResultSet" (sorry for the typo) proofs that: if the same spot color
    name is reused BUT has a different appearance in the alternate colorspace, it
    will be converted immediately to its alternate colorspace.
    "createMapping" is used so that getDeviceN (color) returns the components in
    the correct order according their appearance in the splash bitmap, i.e. the
    fourth detected spot color must be placed in index 7 of the color array.
    updateFill- and updateStrokeColorspace are needed to create this mapping at the
    appropriate place. And they are not called once but everytime the colorspace
    changed in the PDF (but of course only once in Gfx).
    The GooList *getSeparationList() is used to store the functions for converting
    the spot colors to their alternate colorspace in order of their appearance in
    the splash bitmap. The functions are needed to compare if a spot color with the
    same name has really the same appearance and at the end when the splash bitmap
    has to be converted to a CMYK or RGB bitmap (s. ahead).
    deviceNTransfer is needed simular to rgbTransferX or cmykTransferX if a
    transfer function is specified in the ExtGState and splash uses the DeviceN8.
    "Do we really need splashModeDeviceN8?": Do we really need splashModeXBGR8? But
    kidding aside: splashModeDeviceN8 needs four more components than
    splashModeCMYK8, so the bitmap size in memory doubles the size of a pure CMYK
    bitmap, and it is only needed if the PDF uses spot colors. So I think it's a
    good idea to spend an additional mode and let it up to the calling application
    and the cirumstances if it wants to use this new mode or not.

diff --git a/poppler/Function.cc b/poppler/Function.cc
index 2c3aa8a..d26aed8 100644
--- a/poppler/Function.cc
+++ b/poppler/Function.cc
@@ -510,6 +510,20 @@ void SampledFunction::transform(double *in, double *out) {
   }
 }
 
+GBool SampledFunction::hasDifferentResultSet(Function *func) {
+  if (func->getType() == 0) {
+    SampledFunction *compTo = (SampledFunction *) func;
+    if (compTo->getSampleNumber() != nSamples)
+      return gTrue;
+    double *compSamples = compTo->getSamples();
+    for (int i = 0; i < nSamples; i++) {
+      if (samples[i] != compSamples[i])
+        return gTrue;
+    }
+  }
+  return gFalse;
+}
+
 //------------------------------------------------------------------------
 // ExponentialFunction
 //------------------------------------------------------------------------
diff --git a/poppler/Function.h b/poppler/Function.h
index a456dfe..25df133 100644
--- a/poppler/Function.h
+++ b/poppler/Function.h
@@ -16,6 +16,7 @@
 // Copyright (C) 2009, 2010 Albert Astals Cid <aacid at kde.org>
 // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger at googlemail.com>
 // Copyright (C) 2011 Andrea Canciani <ranma42 at gmail.com>
+// Copyright (C) 2012 Thomas Freitag <Thomas.Freitag at alfa.de>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -79,6 +80,7 @@ public:
   double getRangeMin(int i) { return range[i][0]; }
   double getRangeMax(int i) { return range[i][1]; }
   GBool getHasRange() { return hasRange; }
+  virtual GBool hasDifferentResultSet(Function *func) { return gFalse; }
 
   // Transform an input tuple into an output tuple.
   virtual void transform(double *in, double *out) = 0;
@@ -126,6 +128,7 @@ public:
   virtual int getType() { return 0; }
   virtual void transform(double *in, double *out);
   virtual GBool isOk() { return ok; }
+  virtual GBool hasDifferentResultSet(Function *func);
 
   int getSampleSize(int i) { return sampleSize[i]; }
   double getEncodeMin(int i) { return encode[i][0]; }
@@ -133,6 +136,7 @@ public:
   double getDecodeMin(int i) { return decode[i][0]; }
   double getDecodeMax(int i) { return decode[i][1]; }
   double *getSamples() { return samples; }
+  int getSampleNumber() { return nSamples; }
 
 private:
 
diff --git a/poppler/GfxState.cc b/poppler/GfxState.cc
index e3413e5..dc7538a 100644
--- a/poppler/GfxState.cc
+++ b/poppler/GfxState.cc
@@ -248,6 +248,7 @@ cmsHPROFILE GfxColorSpace::getDisplayProfile() {
 
 GfxColorSpace::GfxColorSpace() {
   overprintMask = 0x0f;
+  mapping = NULL;
 }
 
 GfxColorSpace::~GfxColorSpace() {
@@ -321,6 +322,10 @@ GfxColorSpace *GfxColorSpace::parse(Object *csObj, Gfx *gfx, int recursion) {
   return cs;
 }
 
+void GfxColorSpace::createMapping(GooList *separationList, int maxSepComps) {
+  return;
+}
+
 void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange,
 				     int maxImgPixel) {
   int i;
@@ -602,6 +607,12 @@ void GfxDeviceGrayColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->k = clip01(gfxColorComp1 - color->c[0]);
 }
 
+void GfxDeviceGrayColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  deviceN->c[3] = clip01(gfxColorComp1 - color->c[0]);
+}
+
 void GfxDeviceGrayColorSpace::getDefaultColor(GfxColor *color) {
   color->c[0] = 0;
 }
@@ -810,6 +821,17 @@ void GfxCalGrayColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->k = k;
 }
 
+void GfxCalGrayColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  GfxCMYK cmyk;
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  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) {
   color->c[0] = 0;
 }
@@ -895,6 +917,17 @@ void GfxDeviceRGBColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->k = k;
 }
 
+void GfxDeviceRGBColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  GfxCMYK cmyk;
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  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) {
   color->c[0] = 0;
   color->c[1] = 0;
@@ -1127,6 +1160,17 @@ void GfxCalRGBColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->k = k;
 }
 
+void GfxCalRGBColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  GfxCMYK cmyk;
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  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) {
   color->c[0] = 0;
   color->c[1] = 0;
@@ -1227,6 +1271,15 @@ void GfxDeviceCMYKColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->k = clip01(color->c[3]);
 }
 
+void GfxDeviceCMYKColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  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) {
   color->c[0] = 0;
   color->c[1] = 0;
@@ -1453,6 +1506,17 @@ void GfxLabColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->k = k;
 }
 
+void GfxLabColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  GfxCMYK cmyk;
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  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) {
   color->c[0] = 0;
   if (aMin > 0) {
@@ -1858,6 +1922,17 @@ GBool GfxICCBasedColorSpace::useGetRGBLine() {
 #endif
 }
 
+void GfxICCBasedColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  GfxCMYK cmyk;
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  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) {
   int i;
 
@@ -2077,6 +2152,12 @@ void GfxIndexedColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   base->getCMYK(mapColorToBase(color, &color2), cmyk);
 }
 
+void GfxIndexedColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  GfxColor color2;
+
+  base->getDeviceN(mapColorToBase(color, &color2), deviceN);
+}
+
 void GfxIndexedColorSpace::getDefaultColor(GfxColor *color) {
   color->c[0] = 0;
 }
@@ -2107,6 +2188,8 @@ GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA,
     overprintMask = 0x04;
   } else if (!name->cmp("Black")) {
     overprintMask = 0x08;
+  } else if (!name->cmp("All")) {
+    overprintMask = 0xffffffff;
   }
 }
 
@@ -2114,23 +2197,32 @@ GfxSeparationColorSpace::GfxSeparationColorSpace(GooString *nameA,
 						 GfxColorSpace *altA,
 						 Function *funcA,
 						 GBool nonMarkingA,
-						 Guint overprintMaskA) {
+						 Guint 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 != NULL)
+    gfree(mapping);
 }
 
 GfxColorSpace *GfxSeparationColorSpace::copy() {
+  int *mappingA = NULL;
+  if (mapping != NULL) {
+    mappingA = (int *) gmalloc(sizeof(int));
+    *mappingA = *mapping;
+  }
   return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(),
-				     nonMarking, overprintMask);
+				     nonMarking, overprintMask, mappingA);
 }
 
 //~ handle the 'All' and 'None' colorants
@@ -2217,10 +2309,75 @@ void GfxSeparationColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   alt->getCMYK(&color2, cmyk);
 }
 
+void GfxSeparationColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  if (mapping == NULL || 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) {
   color->c[0] = gfxColorComp1;
 }
 
+void GfxSeparationColorSpace::createMapping(GooList *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:
+      Guint newOverprintMask = 0x10;
+      for (int i = 0; i < separationList->getLength(); i++) {
+        GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)separationList->get(i);
+        if (!sepCS->getName()->cmp(name)) {
+          if (sepCS->getFunc()->hasDifferentResultSet(func)) {
+            error(errSyntaxWarning, -1,
+              "Different functions found for '{0:s}', convert immediately", name);
+            gfree(mapping);
+            mapping = NULL;
+            return;
+          }
+          *mapping = i+4;
+          overprintMask = newOverprintMask;
+          return;
+        }
+        newOverprintMask <<=1;
+      }
+      if (separationList->getLength() == maxSepComps) {
+        error(errSyntaxWarning, -1,
+	        "Too many ({0:d}) spots, convert '{1:s}' immediately", maxSepComps, name);
+        gfree(mapping);
+        mapping = NULL;
+        return;
+      }
+      *mapping = separationList->getLength() + 4;
+      separationList->append(copy());
+      overprintMask = newOverprintMask;
+      break;
+  }
+}
+
 //------------------------------------------------------------------------
 // GfxDeviceNColorSpace
 //------------------------------------------------------------------------
@@ -2228,14 +2385,17 @@ void GfxSeparationColorSpace::getDefaultColor(GfxColor *color) {
 GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
 					   GooString **namesA,
 					   GfxColorSpace *altA,
-					   Function *funcA) {
+					   Function *funcA,
+					   GooList *sepsCSA) {
   int i;
 
   nComps = nCompsA;
   alt = altA;
   func = funcA;
+  sepsCS = sepsCSA;
   nonMarking = gTrue;
   overprintMask = 0;
+  mapping = NULL;
   for (i = 0; i < nComps; ++i) {
     names[i] = namesA[i];
     if (names[i]->cmp("None")) {
@@ -2249,6 +2409,8 @@ GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
       overprintMask |= 0x04;
     } else if (!names[i]->cmp("Black")) {
       overprintMask |= 0x08;
+    } else if (!names[i]->cmp("All")) {
+      overprintMask = 0xffffffff;
     } else {
       overprintMask = 0x0f;
     }
@@ -2259,6 +2421,8 @@ GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
 					   GooString **namesA,
 					   GfxColorSpace *altA,
 					   Function *funcA,
+					   GooList *sepsCSA,
+					   int *mappingA,
 					   GBool nonMarkingA,
 					   Guint overprintMaskA) {
   int i;
@@ -2266,6 +2430,8 @@ GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
   nComps = nCompsA;
   alt = altA;
   func = funcA;
+  sepsCS = sepsCSA;
+  mapping = mappingA;
   nonMarking = nonMarkingA;
   overprintMask = overprintMaskA;
   for (i = 0; i < nComps; ++i) {
@@ -2281,11 +2447,25 @@ GfxDeviceNColorSpace::~GfxDeviceNColorSpace() {
   }
   delete alt;
   delete func;
+  deleteGooList(sepsCS, GfxSeparationColorSpace);
+  if (mapping != NULL)
+    gfree(mapping);
 }
 
 GfxColorSpace *GfxDeviceNColorSpace::copy() {
+  int i;
+  int *mappingA = NULL;
+
+  GooList *sepsCSA = new GooList(sepsCS->getLength());
+  for (i = 0; i < sepsCS->getLength(); i++)
+    sepsCSA->append(((GfxSeparationColorSpace *) sepsCS->get(i))->copy());
+  if (mapping != NULL) {
+    mappingA = (int *)gmalloc(sizeof(int) * nComps);
+    for (i = 0; i < nComps; i++)
+      mappingA[i] = mapping[i];
+  }
   return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(),
-				  nonMarking, overprintMask);
+				  sepsCSA, mappingA, nonMarking, overprintMask);
 }
 
 //~ handle the 'None' colorant
@@ -2297,6 +2477,7 @@ GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, Gfx *gfx, int recursion)
   Function *funcA;
   Object obj1, obj2;
   int i;
+  GooList *separationList = new GooList();
 
   if (arr->getLength() != 4 && arr->getLength() != 5) {
     error(errSyntaxWarning, -1, "Bad DeviceN color space");
@@ -2333,7 +2514,26 @@ GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, Gfx *gfx, int recursion)
     goto err4;
   }
   obj1.free();
-  cs = new GfxDeviceNColorSpace(nCompsA, namesA, altA, funcA);
+  if (arr->getLength() == 5) {
+    if (!arr->get(4, &obj1)->isDict()) {
+      error(errSyntaxWarning, -1, "Bad DeviceN color space (attributes)");
+      goto err4;
+    }
+    Dict *attribs = obj1.getDict();
+    attribs->lookup("Colorants", &obj2);
+    if (obj2.isDict()) {
+      Dict *colorants = obj2.getDict();
+      for (i = 0; i < colorants->getLength(); i++) {
+        Object obj3;
+        colorants->getVal(i, &obj3);
+        separationList->append(GfxSeparationColorSpace::parse(obj3.getArray(), gfx, recursion));
+        obj3.free();
+      }
+    }
+    obj2.free();
+    obj1.free();
+  }
+  cs = new GfxDeviceNColorSpace(nCompsA, namesA, altA, funcA, separationList);
   return cs;
 
  err4:
@@ -2345,6 +2545,7 @@ GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, Gfx *gfx, int recursion)
  err2:
   obj1.free();
  err1:
+  delete separationList;
   return NULL;
 }
 
@@ -2393,6 +2594,24 @@ void GfxDeviceNColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   alt->getCMYK(&color2, cmyk);
 }
 
+void GfxDeviceNColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  if (mapping == NULL) {
+    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) {
   int i;
 
@@ -2401,6 +2620,95 @@ void GfxDeviceNColorSpace::getDefaultColor(GfxColor *color) {
   }
 }
 
+void GfxDeviceNColorSpace::createMapping(GooList *separationList, int maxSepComps) {
+  if (nonMarking)               // None
+    return;
+  mapping = (int *)gmalloc(sizeof(int) * nComps);
+  Guint newOverprintMask = 0;
+  for (int i = 0; i < nComps; i++) {
+    if (!names[i]->cmp("None")) {
+      mapping[i] = -1;
+    } else if (!names[i]->cmp("Cyan")) {
+      newOverprintMask |= 0x01;
+      mapping[i] = 0;
+    } else if (!names[i]->cmp("Magenta")) {
+      newOverprintMask |= 0x02;
+      mapping[i] = 1;
+    } else if (!names[i]->cmp("Yellow")) {
+      newOverprintMask |= 0x04;
+      mapping[i] = 2;
+    } else if (!names[i]->cmp("Black")) {
+      newOverprintMask |= 0x08;
+      mapping[i] = 3;
+    } else {
+      Guint startOverprintMask = 0x10;
+      GBool found = gFalse;
+      Function *sepFunc = NULL;
+      if (nComps == 1)
+        sepFunc = func;
+      else {
+        for (int k = 0; k < sepsCS->getLength(); k++) {
+          GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)sepsCS->get(k);
+          if (!sepCS->getName()->cmp(names[i])) {
+            sepFunc = sepCS->getFunc();
+            break;
+          }
+        }
+      }
+      for (int j = 0; j < separationList->getLength(); j++) {
+        GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)separationList->get(j);
+        if (!sepCS->getName()->cmp(names[i])) {
+          if (sepFunc != NULL && sepCS->getFunc()->hasDifferentResultSet(sepFunc)) {
+            error(errSyntaxWarning, -1,
+              "Different functions found for '{0:s}', convert immediately", names[i]);
+            gfree(mapping);
+            mapping = NULL;
+            overprintMask = 0xffffffff;
+            return;
+          }
+          mapping[i] = j+4;
+          newOverprintMask |= startOverprintMask;
+          found = gTrue;
+          break;
+        }
+        startOverprintMask <<=1;
+      }
+      if (!found) {
+        if (separationList->getLength() == maxSepComps) {
+          error(errSyntaxWarning, -1,
+            "Too many ({0:d}) spots, convert '{1:s}' immediately", maxSepComps, names[i]);
+          gfree(mapping);
+          mapping = NULL;
+          overprintMask = 0xffffffff;
+          return;
+        }
+        mapping[i] = separationList->getLength() + 4;
+        newOverprintMask |= startOverprintMask;
+        if (nComps == 1)
+          separationList->append(new GfxSeparationColorSpace(names[i]->copy(),alt->copy(), func->copy()));
+        else {
+          for (int k = 0; k < sepsCS->getLength(); k++) {
+            GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)sepsCS->get(k);
+            if (!sepCS->getName()->cmp(names[i])) {
+              found = gTrue;
+              separationList->append(sepCS->copy());
+              break;
+            }
+          }
+          if(!found) {
+            error(errSyntaxWarning, -1, "DeviceN has no suitable colorant");
+            gfree(mapping);
+            mapping = NULL;
+            overprintMask = 0xffffffff;
+            return;
+          }
+        }
+      }
+    }
+  }
+  overprintMask = newOverprintMask;
+}
+
 //------------------------------------------------------------------------
 // GfxPatternColorSpace
 //------------------------------------------------------------------------
@@ -2456,6 +2764,12 @@ void GfxPatternColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
   cmyk->k = 1;
 }
 
+void GfxPatternColorSpace::getDeviceN(GfxColor *color, GfxColor *deviceN) {
+  for (int i = 0; i < gfxColorMaxComps; i++)
+    deviceN->c[i] = 0;
+  deviceN->c[3] = 1;
+}
+
 void GfxPatternColorSpace::getDefaultColor(GfxColor *color) {
   color->c[0]=0;
 }
@@ -5273,6 +5587,23 @@ void GfxImageColorMap::getCMYK(Guchar *x, GfxCMYK *cmyk) {
   }
 }
 
+void GfxImageColorMap::getDeviceN(Guchar *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(Guchar *x, GfxColor *color) {
   int maxPixel, i;
 
diff --git a/poppler/GfxState.h b/poppler/GfxState.h
index 856ec3f..f2ce6b2 100644
--- a/poppler/GfxState.h
+++ b/poppler/GfxState.h
@@ -20,7 +20,7 @@
 // Copyright (C) 2009-2011 Albert Astals Cid <aacid at kde.org>
 // Copyright (C) 2010 Christian Feuersänger <cfeuersaenger at googlemail.com>
 // Copyright (C) 2011 Andrea Canciani <ranma42 at gmail.com>
-// Copyright (C) 2011 Thomas Freitag <Thomas.Freitag at alfa.de>
+// Copyright (C) 2011, 2012 Thomas Freitag <Thomas.Freitag at alfa.de>
 //
 // To see a description of the changes please see the Changelog file that
 // came with your tarball or type make ChangeLog if you are building from git
@@ -48,6 +48,7 @@ class GfxFont;
 class PDFRectangle;
 class GfxShading;
 class PopplerCache;
+class GooList;
 
 class Matrix {
 public:
@@ -202,11 +203,15 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray) = 0;
   virtual void getRGB(GfxColor *color, GfxRGB *rgb) = 0;
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk) = 0;
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN) = 0;
   virtual void getGrayLine(Guchar * /*in*/, Guchar * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getGrayLine this should not happen"); }
   virtual void getRGBLine(Guchar * /*in*/, unsigned int * /*out*/, int /*length*/) { error(errInternal, -1, "GfxColorSpace::getRGBLine (first variant) this should not happen"); }
   virtual void getRGBLine(Guchar * /*in*/, Guchar * /*out*/, int /*length*/) {  error(errInternal, -1, "GfxColorSpace::getRGBLine (second variant) this should not happen"); }
   virtual void getRGBXLine(Guchar * /*in*/, Guchar * /*out*/, int /*length*/) {  error(errInternal, -1, "GfxColorSpace::getRGBXLine this should not happen"); }
 
+  // create mapping for spot colorants
+  virtual void createMapping(GooList *separationList, int maxSepComps);
+
   // Does this ColorSpace support getRGBLine?
   virtual GBool useGetRGBLine() { return gFalse; }
   // Does this ColorSpace support getGrayLine?
@@ -249,6 +254,7 @@ public:
 protected:
 
   Guint overprintMask;
+  int *mapping;
 };
 
 //------------------------------------------------------------------------
@@ -266,6 +272,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
   virtual void getGrayLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
@@ -298,6 +305,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
 
   virtual int getNComps() { return 1; }
   virtual void getDefaultColor(GfxColor *color);
@@ -335,6 +343,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
   virtual void getGrayLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
@@ -367,6 +376,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
 
   virtual int getNComps() { return 3; }
   virtual void getDefaultColor(GfxColor *color);
@@ -408,6 +418,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
@@ -437,6 +448,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
 
   virtual int getNComps() { return 3; }
   virtual void getDefaultColor(GfxColor *color);
@@ -484,6 +496,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
@@ -529,6 +542,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
   virtual void getRGBLine(Guchar *in, unsigned int *out, int length);
   virtual void getRGBLine(Guchar *in, Guchar *out, int length);
   virtual void getRGBXLine(Guchar *in, Guchar *out, int length);
@@ -546,6 +560,10 @@ public:
   int getIndexHigh() { return indexHigh; }
   Guchar *getLookup() { return lookup; }
   GfxColor *mapColorToBase(GfxColor *color, GfxColor *baseColor);
+  Guint getOverprintMask() { return base->getOverprintMask(); }
+  virtual void createMapping(GooList *separationList, int maxSepComps)
+    { base->createMapping(separationList, maxSepComps); }
+
 
 private:
 
@@ -573,6 +591,9 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
+
+  virtual void createMapping(GooList *separationList, int maxSepComps);
 
   virtual int getNComps() { return 1; }
   virtual void getDefaultColor(GfxColor *color);
@@ -588,7 +609,7 @@ private:
 
   GfxSeparationColorSpace(GooString *nameA, GfxColorSpace *altA,
 			  Function *funcA, GBool nonMarkingA,
-			  Guint overprintMaskA);
+			  Guint overprintMaskA, int *mappingA);
 
   GooString *name;		// colorant name
   GfxColorSpace *alt;		// alternate color space
@@ -604,7 +625,7 @@ class GfxDeviceNColorSpace: public GfxColorSpace {
 public:
 
   GfxDeviceNColorSpace(int nCompsA, GooString **namesA,
-		       GfxColorSpace *alt, Function *func);
+		       GfxColorSpace *alt, Function *func, GooList *sepsCS);
   virtual ~GfxDeviceNColorSpace();
   virtual GfxColorSpace *copy();
   virtual GfxColorSpaceMode getMode() { return csDeviceN; }
@@ -615,6 +636,9 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
+
+  virtual void createMapping(GooList *separationList, int maxSepComps);
 
   virtual int getNComps() { return nComps; }
   virtual void getDefaultColor(GfxColor *color);
@@ -629,8 +653,8 @@ public:
 private:
 
   GfxDeviceNColorSpace(int nCompsA, GooString **namesA,
-		       GfxColorSpace *alt, Function *func,
-		       GBool nonMarkingA, Guint overprintMaskA);
+		       GfxColorSpace *alt, Function *func, GooList *sepsCSA,
+		       int *mappingA, GBool nonMarkingA, Guint overprintMaskA);
 
   int nComps;			// number of components
   GooString			// colorant names
@@ -638,6 +662,7 @@ private:
   GfxColorSpace *alt;		// alternate color space
   Function *func;		// tint transform (into alternate color space)
   GBool nonMarking;
+  GooList *sepsCS; // list of separation cs for spot colorants;
 };
 
 //------------------------------------------------------------------------
@@ -658,6 +683,7 @@ public:
   virtual void getGray(GfxColor *color, GfxGray *gray);
   virtual void getRGB(GfxColor *color, GfxRGB *rgb);
   virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+  virtual void getDeviceN(GfxColor *color, GfxColor *deviceN);
 
   virtual int getNComps() { return 0; }
   virtual void getDefaultColor(GfxColor *color);
@@ -1107,6 +1133,7 @@ public:
   void getRGBXLine(Guchar *in, Guchar *out, int length);
   void getGrayLine(Guchar *in, Guchar *out, int length);
   void getCMYK(Guchar *x, GfxCMYK *cmyk);
+  void getDeviceN(Guchar *x, GfxColor *deviceN);
   void getColor(Guchar *x, GfxColor *color);
 
 private:
@@ -1342,8 +1369,12 @@ public:
     { strokeColorSpace->getRGB(&strokeColor, rgb); }
   void getFillCMYK(GfxCMYK *cmyk)
     { fillColorSpace->getCMYK(&fillColor, cmyk); }
+  void getFillDeviceN(GfxColor *deviceN)
+    { fillColorSpace->getDeviceN(&fillColor, deviceN); }
   void getStrokeCMYK(GfxCMYK *cmyk)
     { strokeColorSpace->getCMYK(&strokeColor, cmyk); }
+  void getStrokeDeviceN(GfxColor *deviceN)
+    { strokeColorSpace->getDeviceN(&strokeColor, deviceN); }
   GfxColorSpace *getFillColorSpace() { return fillColorSpace; }
   GfxColorSpace *getStrokeColorSpace() { return strokeColorSpace; }
   GfxPattern *getFillPattern() { return fillPattern; }
diff --git a/poppler/SplashOutputDev.cc b/poppler/SplashOutputDev.cc
index 9e07060..05e9dea 100644
--- a/poppler/SplashOutputDev.cc
+++ b/poppler/SplashOutputDev.cc
@@ -94,6 +94,7 @@ static inline void convertGfxColor(SplashColorPtr dest,
   GfxRGB rgb;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
 
   // make gcc happy
@@ -124,6 +125,11 @@ static inline void convertGfxColor(SplashColorPtr dest,
       color[2] = colToByte(cmyk.y);
       color[3] = colToByte(cmyk.k);
     break;
+    case splashModeDeviceN8:
+      colorSpace->getDeviceN(src, &deviceN);
+      for (int i = 0; i < SPOT_NCOMPS + 4; i++)
+        color[i] = colToByte(deviceN.c[i]);
+    break;
 #endif
   }
   splashColorCopy(dest, color);
@@ -154,6 +160,8 @@ void SplashGouraudPattern::getParameterizedColor(double colorinterp, SplashColor
 #if SPLASH_CMYK
   if (mode == splashModeCMYK8)
     colorComps=4;
+  else if (mode == splashModeDeviceN8)
+    colorComps=4 + SPOT_NCOMPS;
 #endif
 
   shading->getParameterizedColor(colorinterp, &src);
@@ -436,7 +444,7 @@ static void splashOutBlendMultiply(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -460,7 +468,7 @@ static void splashOutBlendScreen(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -484,7 +492,7 @@ static void splashOutBlendOverlay(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -512,7 +520,7 @@ static void splashOutBlendDarken(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -536,7 +544,7 @@ static void splashOutBlendLighten(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -561,7 +569,7 @@ static void splashOutBlendColorDodge(SplashColorPtr src, SplashColorPtr dest,
   int i, x;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -595,7 +603,7 @@ static void splashOutBlendColorBurn(SplashColorPtr src, SplashColorPtr dest,
   int i, x;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -629,7 +637,7 @@ static void splashOutBlendHardLight(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -657,7 +665,7 @@ static void splashOutBlendSoftLight(SplashColorPtr src, SplashColorPtr dest,
   int i, x;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -700,7 +708,7 @@ static void splashOutBlendDifference(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -724,7 +732,7 @@ static void splashOutBlendExclusion(SplashColorPtr src, SplashColorPtr dest,
   int i;
 
 #if SPLASH_CMYK
-  if (cm == splashModeCMYK8) {
+  if (cm == splashModeCMYK8 || cm == splashModeDeviceN8) {
     SplashColor rgbSrc;
     SplashColor rgbDest;
     SplashColor rgbBlend;
@@ -859,6 +867,7 @@ static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest,
     break;
 #if SPLASH_CMYK
   case splashModeCMYK8:
+  case splashModeDeviceN8:
     for (i = 0; i < 4; i++) {
       // convert to additive
       src2[i] = 0xff - src[i];
@@ -908,6 +917,7 @@ static void splashOutBlendSaturation(SplashColorPtr src, SplashColorPtr dest,
     break;
 #if SPLASH_CMYK
   case splashModeCMYK8:
+  case splashModeDeviceN8:
     for (i = 0; i < 4; i++) {
       // convert to additive
       src2[i] = 0xff - src[i];
@@ -952,6 +962,7 @@ static void splashOutBlendColor(SplashColorPtr src, SplashColorPtr dest,
     break;
 #if SPLASH_CMYK
   case splashModeCMYK8:
+  case splashModeDeviceN8:
     for (i = 0; i < 4; i++) {
       // convert to additive
       src2[i] = 0xff - src[i];
@@ -995,6 +1006,7 @@ static void splashOutBlendLuminosity(SplashColorPtr src, SplashColorPtr dest,
     break;
 #if SPLASH_CMYK
   case splashModeCMYK8:
+  case splashModeDeviceN8:
     for (i = 0; i < 4; i++) {
       // convert to additive
       src2[i] = 0xff - src[i];
@@ -1391,6 +1403,10 @@ void SplashOutputDev::startPage(int pageNum, GfxState *state) {
   case splashModeCMYK8:
     color[0] = color[1] = color[2] = color[3] = 0;
     break;
+  case splashModeDeviceN8:
+    for (int i = 0; i < 4 + SPOT_NCOMPS; i++)
+      color[i] = 0;
+    break;
 #endif
   }
   splash->setStrokePattern(new SplashSolidColor(color));
@@ -1429,7 +1445,9 @@ void SplashOutputDev::updateAll(GfxState *state) {
   updateFlatness(state);
   updateMiterLimit(state);
   updateStrokeAdjust(state);
+  updateFillColorSpace(state);
   updateFillColor(state);
+  updateStrokeColorSpace(state);
   updateStrokeColor(state);
   needFontUpdate = gTrue;
 }
@@ -1500,11 +1518,26 @@ void SplashOutputDev::updateStrokeAdjust(GfxState * /*state*/) {
 #endif
 }
 
+void SplashOutputDev::updateFillColorSpace(GfxState *state) {
+#if SPLASH_CMYK
+  if (colorMode == splashModeDeviceN8)
+    state->getFillColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS);
+#endif
+}
+
+void SplashOutputDev::updateStrokeColorSpace(GfxState *state) {
+#if SPLASH_CMYK
+  if (colorMode == splashModeDeviceN8)
+    state->getStrokeColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS);
+#endif
+}
+
 void SplashOutputDev::updateFillColor(GfxState *state) {
   GfxGray gray;
   GfxRGB rgb;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
 
   switch (colorMode) {
@@ -1524,6 +1557,10 @@ void SplashOutputDev::updateFillColor(GfxState *state) {
     state->getFillCMYK(&cmyk);
     splash->setFillPattern(getColor(&cmyk));
     break;
+  case splashModeDeviceN8:
+    state->getFillDeviceN(&deviceN);
+    splash->setFillPattern(getColor(&deviceN));
+    break;
 #endif
   }
 }
@@ -1533,6 +1570,7 @@ void SplashOutputDev::updateStrokeColor(GfxState *state) {
   GfxRGB rgb;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
 
   switch (colorMode) {
@@ -1552,6 +1590,10 @@ void SplashOutputDev::updateStrokeColor(GfxState *state) {
     state->getStrokeCMYK(&cmyk);
     splash->setStrokePattern(getColor(&cmyk));
     break;
+  case splashModeDeviceN8:
+    state->getStrokeDeviceN(&deviceN);
+    splash->setStrokePattern(getColor(&deviceN));
+    break;
 #endif
   }
 }
@@ -1596,6 +1638,14 @@ SplashPattern *SplashOutputDev::getColor(GfxCMYK *cmyk) {
   color[3] = colToByte(cmyk->k);
   return new SplashSolidColor(color);
 }
+
+SplashPattern *SplashOutputDev::getColor(GfxColor *deviceN) {
+  SplashColor color;
+
+  for (int i = 0; i < 4 + SPOT_NCOMPS; i++)
+    color[i] = colToByte(deviceN->c[i]);
+  return new SplashSolidColor(color);
+}
 #endif
 
 void SplashOutputDev::setOverprintMask(GfxColorSpace *colorSpace,
@@ -2717,6 +2767,7 @@ GBool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine,
   GfxGray gray;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
   int nComps, x;
 
@@ -2765,6 +2816,13 @@ GBool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine,
 	*q++ = col[3];
       }
       break;
+    case splashModeDeviceN8:
+      for (x = 0, q = colorLine; x < imgData->width; ++x, ++p) {
+	col = &imgData->lookup[(SPOT_NCOMPS+4) * *p];
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    *q++ = col[cp];
+      }
+      break;
 #endif
     }
   } else {
@@ -2804,6 +2862,13 @@ GBool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine,
 	*q++ = colToByte(cmyk.k);
       }
       break;
+    case splashModeDeviceN8:
+      for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) {
+	imgData->colorMap->getDeviceN(p, &deviceN);
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    *q++ = colToByte(deviceN.c[cp]);
+      }
+      break;
 #endif
     }
   }
@@ -2821,6 +2886,7 @@ GBool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine,
   GfxGray gray;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
   Guchar alpha;
   int nComps, x, i;
@@ -2873,6 +2939,11 @@ GBool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine,
 	*q++ = col[2];
 	*q++ = col[3];
 	break;
+      case splashModeDeviceN8:
+	col = &imgData->lookup[(SPOT_NCOMPS+4) * *p];
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    *q++ = col[cp];
+  break;
 #endif
       }
       *aq++ = alpha;
@@ -2900,6 +2971,11 @@ GBool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine,
 	*q++ = colToByte(cmyk.y);
 	*q++ = colToByte(cmyk.k);
 	break;
+    case splashModeDeviceN8:
+	imgData->colorMap->getDeviceN(p, &deviceN);
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    *q++ = colToByte(deviceN.c[cp]);
+	break;
 #endif
       }
       *aq++ = alpha;
@@ -2976,7 +3052,7 @@ GBool SplashOutputDev::tilingBitmapSrc(void *data, SplashColorPtr colorLine,
         imgData->pattern->getColor(x, imgData->y, pat);
         for (int i = 0; i < splashColorModeNComps[imgData->colorMode]; ++i) {
 #if SPLASH_CMYK
-          if (imgData->colorMode == splashModeCMYK8)
+          if (imgData->colorMode == splashModeCMYK8 || imgData->colorMode == splashModeDeviceN8)
             dest[i] = div255(pat[i] * (255 - col[0]));
           else
 #endif
@@ -3017,6 +3093,7 @@ void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
 #if SPLASH_CMYK
   GfxCMYK cmyk;
   GBool grayIndexed = gFalse;
+  GfxColor deviceN;
 #endif
   Guchar pix;
   int n, i;
@@ -3096,6 +3173,21 @@ void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
 	imgData.lookup[4*i+3] = colToByte(cmyk.k);
       }
       break;
+    case splashModeDeviceN8:
+      colorMap->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS);
+      grayIndexed = colorMap->getColorSpace()->getMode() != csDeviceGray;
+      imgData.lookup = (SplashColorPtr)gmallocn(n, SPOT_NCOMPS+4);
+      for (i = 0; i < n; ++i) {
+        pix = (Guchar)i;
+        colorMap->getCMYK(&pix, &cmyk);
+        if (cmyk.c != 0 || cmyk.m != 0 || cmyk.y != 0) {
+          grayIndexed = gFalse;
+        }
+        colorMap->getDeviceN(&pix, &deviceN);
+        for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+          imgData.lookup[(SPOT_NCOMPS+4)*i +cp] = colToByte(deviceN.c[cp]);
+      }
+      break;
 #endif
     }
   }
@@ -3146,6 +3238,7 @@ GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine,
   GfxGray gray;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
   Guchar alpha;
   Guchar *maskPtr;
@@ -3200,6 +3293,11 @@ GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine,
 	*q++ = col[2];
 	*q++ = col[3];
 	break;
+      case splashModeDeviceN8:
+	col = &imgData->lookup[(SPOT_NCOMPS+4) * *p];
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    *q++ = col[cp];
+	break;
 #endif
       }
       *aq++ = alpha;
@@ -3227,6 +3325,11 @@ GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine,
 	*q++ = colToByte(cmyk.y);
 	*q++ = colToByte(cmyk.k);
 	break;
+      case splashModeDeviceN8:
+	imgData->colorMap->getDeviceN(p, &deviceN);
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    *q++ = colToByte(deviceN.c[cp]);
+	break;
 #endif
       }
       *aq++ = alpha;
@@ -3258,10 +3361,14 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref,
   GfxRGB rgb;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
   Guchar pix;
   int n, i;
 
+#if SPLASH_CMYK
+  colorMap->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS);
+#endif
   setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(),
 		   state->getOverprintMode(), NULL);
 
@@ -3383,6 +3490,15 @@ void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref,
 	  imgData.lookup[4*i+3] = colToByte(cmyk.k);
 	}
 	break;
+      case splashModeDeviceN8:
+	imgData.lookup = (SplashColorPtr)gmallocn(n, SPOT_NCOMPS+4);
+	for (i = 0; i < n; ++i) {
+	  pix = (Guchar)i;
+	  colorMap->getDeviceN(&pix, &deviceN);
+    for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      imgData.lookup[(SPOT_NCOMPS+4)*i + cp] = colToByte(deviceN.c[cp]);
+	}
+	break;
 #endif
       }
     }
@@ -3421,10 +3537,14 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref,
   GfxRGB rgb;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
   Guchar pix;
   int n, i;
 
+#if SPLASH_CMYK
+  colorMap->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS);
+#endif
   setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(),
 		   state->getOverprintMode(), NULL);
 
@@ -3533,6 +3653,15 @@ void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref,
 	imgData.lookup[4*i+3] = colToByte(cmyk.k);
       }
       break;
+    case splashModeDeviceN8:
+      imgData.lookup = (SplashColorPtr)gmallocn(n, SPOT_NCOMPS+4);
+      for (i = 0; i < n; ++i) {
+	pix = (Guchar)i;
+	colorMap->getDeviceN(&pix, &deviceN);
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    imgData.lookup[(SPOT_NCOMPS+4)*i + cp] = colToByte(deviceN.c[cp]);
+      }
+      break;
 #endif
     }
   }
@@ -3676,7 +3805,7 @@ void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox,
 
   // create the temporary bitmap
   bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue,
-			    bitmapTopDown);
+			    bitmapTopDown, bitmap->getSeparationList());
   splash = new Splash(bitmap, vectorAntialias,
 		      transpGroup->origSplash->getScreen());
   splash->setMinLineWidth(globalParams->getMinLineWidth());
@@ -3765,6 +3894,7 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox,
   GfxRGB rgb;
 #if SPLASH_CMYK
   GfxCMYK cmyk;
+  GfxColor deviceN;
 #endif
   double lum, lum2;
   int tx, ty, x, y;
@@ -3808,6 +3938,12 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox,
 	color[3] = colToByte(cmyk.k);
 	tSplash->compositeBackground(color);
 	break;
+      case splashModeDeviceN8:
+	transpGroupStack->blendingColorSpace->getDeviceN(backdropColor, &deviceN);
+  for (int cp=0; cp < SPOT_NCOMPS+4; cp++)
+    color[cp] = colToByte(deviceN.c[cp]);
+	tSplash->compositeBackground(color);
+	break;
 #endif
       }
       delete tSplash;
@@ -3849,6 +3985,7 @@ void SplashOutputDev::setSoftMask(GfxState *state, double *bbox,
 	    break;
 #if SPLASH_CMYK
 	  case splashModeCMYK8:
+    case splashModeDeviceN8:
 	    lum = (1 - color[3] / 255.0)
 	          - (0.3 / 255.0) * color[0]
 	          - (0.59 / 255.0) * color[1]
@@ -4047,7 +4184,9 @@ GBool SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx1, Catalog *ca
   memset(bitmap->getAlphaPtr(), 0, bitmap->getWidth() * bitmap->getHeight());
   if (paintType == 2) {
 #if SPLASH_CMYK
-    memset(bitmap->getDataPtr(), (colorMode == splashModeCMYK8) ? 0x00 : 0xFF, bitmap->getRowSize() * bitmap->getHeight());
+    memset(bitmap->getDataPtr(), 
+      (colorMode == splashModeCMYK8 || colorMode == splashModeDeviceN8) ? 0x00 : 0xFF, 
+      bitmap->getRowSize() * bitmap->getHeight());
 #else
     memset(bitmap->getDataPtr(), 0xFF, bitmap->getRowSize() * bitmap->getHeight());
 #endif
@@ -4107,7 +4246,8 @@ GBool SplashOutputDev::gouraudTriangleShadedFill(GfxState *state, GfxGouraudTria
     break;
 #if SPLASH_CMYK
     case splashModeCMYK8:
-      bDirectColorTranslation = (shadingMode == csDeviceCMYK);
+    case splashModeDeviceN8:
+      bDirectColorTranslation = (shadingMode == csDeviceCMYK || shadingMode == csDeviceN);
     break;
 #endif
     default:
@@ -4177,6 +4317,9 @@ GBool SplashOutputDev::univariateShadedFill(GfxState *state, SplashUnivariatePat
   state->closePath();
   path = convertPath(state, state->getPath(), gTrue);
 
+#if SPLASH_CMYK
+  pattern->getShading()->getColorSpace()->createMapping(bitmap->getSeparationList(), SPOT_NCOMPS);
+#endif
   setOverprintMask(pattern->getShading()->getColorSpace(), state->getFillOverprint(),
 		   state->getOverprintMode(), state->getFillColor());
   retVal = (splash->shadedFill(path, pattern->getShading()->getHasBBox(), pattern) == splashOk);
diff --git a/poppler/SplashOutputDev.h b/poppler/SplashOutputDev.h
index f1c87ec..de7934d 100644
--- a/poppler/SplashOutputDev.h
+++ b/poppler/SplashOutputDev.h
@@ -215,6 +215,8 @@ public:
   virtual void updateMiterLimit(GfxState *state);
   virtual void updateLineWidth(GfxState *state);
   virtual void updateStrokeAdjust(GfxState *state);
+  virtual void updateFillColorSpace(GfxState *state);
+  virtual void updateStrokeColorSpace(GfxState *state);
   virtual void updateFillColor(GfxState *state);
   virtual void updateStrokeColor(GfxState *state);
   virtual void updateBlendMode(GfxState *state);
@@ -362,6 +364,7 @@ private:
   SplashPattern *getColor(GfxRGB *rgb);
 #if SPLASH_CMYK
   SplashPattern *getColor(GfxCMYK *cmyk);
+  SplashPattern *getColor(GfxColor *deviceN);
 #endif
   void setOverprintMask(GfxColorSpace *colorSpace, GBool overprintFlag,
 			int overprintMode, GfxColor *singleColor, GBool grayIndexed = gFalse);
diff --git a/splash/Splash.cc b/splash/Splash.cc
index e6559f4..e46a496 100644
--- a/splash/Splash.cc
+++ b/splash/Splash.cc
@@ -36,6 +36,7 @@
 #include <math.h>
 #include "goo/gmem.h"
 #include "goo/GooLikely.h"
+#include "goo/GooList.h"
 #include "poppler/Error.h"
 #include "SplashErrorCodes.h"
 #include "SplashMath.h"
@@ -170,7 +171,8 @@ SplashPipeResultColorCtrl Splash::pipeResultColorNoAlphaBlend[] = {
   splashPipeResultColorNoAlphaBlendRGB
 #if SPLASH_CMYK
   ,
-  splashPipeResultColorNoAlphaBlendCMYK
+  splashPipeResultColorNoAlphaBlendCMYK,
+  splashPipeResultColorNoAlphaBlendDeviceN
 #endif
 };
 
@@ -182,7 +184,8 @@ SplashPipeResultColorCtrl Splash::pipeResultColorAlphaNoBlend[] = {
   splashPipeResultColorAlphaNoBlendRGB
 #if SPLASH_CMYK
   ,
-  splashPipeResultColorAlphaNoBlendCMYK
+  splashPipeResultColorAlphaNoBlendCMYK,
+  splashPipeResultColorAlphaNoBlendDeviceN
 #endif
 };
 
@@ -194,7 +197,8 @@ SplashPipeResultColorCtrl Splash::pipeResultColorAlphaBlend[] = {
   splashPipeResultColorAlphaBlendRGB
 #if SPLASH_CMYK
   ,
-  splashPipeResultColorAlphaBlendCMYK
+  splashPipeResultColorAlphaBlendCMYK,
+  splashPipeResultColorAlphaBlendDeviceN
 #endif
 };
 
@@ -307,6 +311,8 @@ inline void Splash::pipeInit(SplashPipe *pipe, int x, int y,
 #if SPLASH_CMYK
     } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) {
       pipe->run = &Splash::pipeRunSimpleCMYK8;
+    } else if (bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunSimpleDeviceN8;
 #endif
     }
   } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask &&
@@ -326,6 +332,8 @@ inline void Splash::pipeInit(SplashPipe *pipe, int x, int y,
 #if SPLASH_CMYK
     } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) {
       pipe->run = &Splash::pipeRunAACMYK8;
+    } else if (bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr) {
+      pipe->run = &Splash::pipeRunAADeviceN8;
 #endif
     }
   }
@@ -338,6 +346,10 @@ void Splash::pipeRun(SplashPipe *pipe) {
   SplashColorPtr cSrc;
   Guchar cResult0, cResult1, cResult2, cResult3;
   int t;
+#if SPLASH_CMYK
+  int cp, mask;
+  Guchar cResult[SPOT_NCOMPS+4];
+#endif
 
   //----- source color
 
@@ -412,6 +424,16 @@ void Splash::pipeRun(SplashPipe *pipe) {
       }
       pipe->destColorPtr += 4;
       break;
+    case splashModeDeviceN8:
+      mask = 1;
+      for (cp = 0; cp < SPOT_NCOMPS + 4; cp ++) {
+        if (state->overprintMask & mask) {
+          pipe->destColorPtr[cp] = state->deviceNTransfer[cp][pipe->cSrc[cp]];
+        }
+        mask <<= 1;
+      }
+      pipe->destColorPtr += (SPOT_NCOMPS+4);
+      break;
 #endif
     }
     if (pipe->destAlphaPtr) {
@@ -452,6 +474,10 @@ void Splash::pipeRun(SplashPipe *pipe) {
       cDest[2] = pipe->destColorPtr[2];
       cDest[3] = pipe->destColorPtr[3];
       break;
+    case splashModeDeviceN8:
+      for (cp = 0; cp < SPOT_NCOMPS + 4; cp++)
+        cDest[cp] = pipe->destColorPtr[cp];
+      break;
 #endif
     }
     if (pipe->destAlphaPtr) {
@@ -489,6 +515,10 @@ void Splash::pipeRun(SplashPipe *pipe) {
 	t = (aDest * 255) / pipe->shape - aDest;
 	switch (bitmap->mode) {
 #if SPLASH_CMYK
+	case splashModeDeviceN8:
+	  for (cp = 4; cp < SPOT_NCOMPS + 4; cp++)
+	    cSrcNonIso[cp] = clip255(pipe->cSrc[cp] +
+				  ((pipe->cSrc[cp] - cDest[cp]) * t) / 255);
 	case splashModeCMYK8:
 	  cSrcNonIso[3] = clip255(pipe->cSrc[3] +
 				  ((pipe->cSrc[3] - cDest[3]) * t) / 255);
@@ -570,6 +600,11 @@ void Splash::pipeRun(SplashPipe *pipe) {
       cResult3 = state->cmykTransferK[div255((255 - aDest) * cSrc[3] +
 					     aDest * cBlend[3])];
       break;
+    case splashPipeResultColorNoAlphaBlendDeviceN:
+      for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+        cResult[cp] = state->deviceNTransfer[cp][div255((255 - aDest) * cSrc[cp] +
+					     aDest * cBlend[cp])];
+      break;
 #endif
 
     case splashPipeResultColorAlphaNoBlendMono:
@@ -612,6 +647,16 @@ void Splash::pipeRun(SplashPipe *pipe) {
 					 aSrc * cSrc[3]) / alphaI];
       }
       break;
+    case splashPipeResultColorAlphaNoBlendDeviceN:
+      if (alphaI == 0) {
+        for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+          cResult[cp] = 0;
+      } else {
+        for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+          cResult[cp] = state->deviceNTransfer[cp][((alphaI - aSrc) * cDest[cp] +
+					 aSrc * cSrc[cp]) / alphaI];
+      }
+      break;
 #endif
 
     case splashPipeResultColorAlphaBlendMono:
@@ -670,6 +715,18 @@ void Splash::pipeRun(SplashPipe *pipe) {
 					alphaI];
       }
       break;
+    case splashPipeResultColorAlphaBlendDeviceN:
+      if (alphaI == 0) {
+        for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+          cResult[cp] = 0;
+      } else {
+        for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+          cResult[cp] = state->deviceNTransfer[cp][((alphaI - aSrc) * cDest[cp] +
+            aSrc * ((255 - alphaIm1) * cSrc[cp] +
+						alphaIm1 * cBlend[cp]) / 255) /
+					  alphaI];
+      }
+      break;
 #endif
     }
 
@@ -730,6 +787,16 @@ void Splash::pipeRun(SplashPipe *pipe) {
       }
       pipe->destColorPtr += 4;
       break;
+    case splashModeDeviceN8:
+      mask = 1;
+      for (cp = 0; cp < SPOT_NCOMPS+4; cp++) {
+        if (state->overprintMask & mask) {
+          pipe->destColorPtr[cp] = cResult[cp];
+        }
+        mask <<=1;
+      }
+      pipe->destColorPtr += (SPOT_NCOMPS+4);
+      break;
 #endif
     }
     if (pipe->destAlphaPtr) {
@@ -844,8 +911,25 @@ void Splash::pipeRunSimpleCMYK8(SplashPipe *pipe) {
 
   ++pipe->x;
 }
-#endif
 
+// special case:
+// !pipe->pattern && pipe->noTransparency && !state->blendFunc &&
+// bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr) {
+void Splash::pipeRunSimpleDeviceN8(SplashPipe *pipe) {
+  //----- write destination pixel
+  int mask = 1;
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++) {
+    if (state->overprintMask & mask) {
+      pipe->destColorPtr[cp] = state->deviceNTransfer[cp][pipe->cSrc[cp]];
+    }
+    mask <<=1;
+  }
+  pipe->destColorPtr += (SPOT_NCOMPS+4);
+  *pipe->destAlphaPtr++ = 255;
+
+  ++pipe->x;
+}
+#endif
 
 // special case:
 // !pipe->pattern && !pipe->noTransparency && !state->softMask &&
@@ -1125,6 +1209,53 @@ void Splash::pipeRunAACMYK8(SplashPipe *pipe) {
 
   ++pipe->x;
 }
+
+// special case:
+// !pipe->pattern && !pipe->noTransparency && !state->softMask &&
+// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
+// !pipe->nonIsolatedGroup &&
+// bitmap->mode == splashModeDeviceN8 && pipe->destAlphaPtr
+void Splash::pipeRunAADeviceN8(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, aResult;
+  SplashColor cDest;
+  Guchar cResult[SPOT_NCOMPS+4];
+  int cp, mask;
+
+  //----- read destination pixel
+  for (cp=0; cp < SPOT_NCOMPS+4; cp++)
+    cDest[cp] = pipe->destColorPtr[cp];
+  aDest = *pipe->destAlphaPtr;
+
+  //----- source alpha
+  aSrc = div255(pipe->aInput * pipe->shape);
+
+  //----- result alpha and non-isolated group element correction
+  aResult = aSrc + aDest - div255(aSrc * aDest);
+  alpha2 = aResult;
+
+  //----- result color
+  if (alpha2 == 0) {
+    for (cp=0; cp < SPOT_NCOMPS+4; cp++)
+      cResult[cp] = 0;
+  } else {
+    for (cp=0; cp < SPOT_NCOMPS+4; cp++)
+      cResult[cp] = state->deviceNTransfer[cp][(Guchar)(((alpha2 - aSrc) * cDest[cp] +
+					      aSrc * pipe->cSrc[cp]) / alpha2)];
+  }
+
+  //----- write destination pixel
+  mask = 1;
+  for (cp=0; cp < SPOT_NCOMPS+4; cp++) {
+    if (state->overprintMask & mask) {
+      pipe->destColorPtr[cp] = cResult[cp];
+    }
+    mask <<= 1;
+  }
+  pipe->destColorPtr += (SPOT_NCOMPS+4);
+  *pipe->destAlphaPtr++ = aResult;
+
+  ++pipe->x;
+}
 #endif
 
 inline void Splash::pipeSetXY(SplashPipe *pipe, int x, int y) {
@@ -1153,6 +1284,9 @@ inline void Splash::pipeSetXY(SplashPipe *pipe, int x, int y) {
   case splashModeCMYK8:
     pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x];
     break;
+  case splashModeDeviceN8:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + (SPOT_NCOMPS + 4) * x];
+    break;
 #endif
   }
   if (bitmap->alpha) {
@@ -1195,6 +1329,9 @@ inline void Splash::pipeIncX(SplashPipe *pipe) {
   case splashModeCMYK8:
     pipe->destColorPtr += 4;
     break;
+  case splashModeDeviceN8:
+    pipe->destColorPtr += (SPOT_NCOMPS+4);
+    break;
 #endif
   }
   if (pipe->destAlphaPtr) {
@@ -1774,6 +1911,17 @@ void Splash::clear(SplashColorPtr color, Guchar alpha) {
       }
     }
     break;
+  case splashModeDeviceN8:
+    row = bitmap->data;
+    for (y = 0; y < bitmap->height; ++y) {
+      p = row;
+      for (x = 0; x < bitmap->width; ++x) {
+        for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+          *p++ = color[cp];
+      }
+      row += bitmap->rowSize;
+    }
+    break;
 #endif
   }
 
@@ -3355,7 +3503,7 @@ void Splash::blitMask(SplashBitmap *src, int xDest, int yDest,
 SplashError Splash::drawImage(SplashImageSource src, void *srcData,
 			      SplashColorMode srcMode, GBool srcAlpha,
 			      int w, int h, SplashCoord *mat,
-			      GBool tilingPattern) {
+            GBool tilingPattern) {
   GBool ok;
   SplashBitmap *scaledImg;
   SplashClipResult clipRes;
@@ -3396,6 +3544,10 @@ SplashError Splash::drawImage(SplashImageSource src, void *srcData,
     ok = srcMode == splashModeCMYK8;
     nComps = 4;
     break;
+  case splashModeDeviceN8:
+    ok = srcMode == splashModeDeviceN8;
+    nComps = SPOT_NCOMPS+4;
+    break;
 #endif
   default:
     ok = gFalse;
@@ -3496,7 +3648,7 @@ SplashError Splash::arbitraryTransformImage(SplashImageSource src, void *srcData
 				     GBool srcAlpha,
 				     int srcWidth, int srcHeight,
 				     SplashCoord *mat,
-				     GBool tilingPattern) {
+             GBool tilingPattern) {
   SplashBitmap *scaledImg;
   SplashClipResult clipRes, clipRes2;
   SplashPipe pipe;
@@ -3621,7 +3773,7 @@ SplashError Splash::arbitraryTransformImage(SplashImageSource src, void *srcData
   }
   scaledImg = scaleImage(src, srcData, srcMode, nComps, srcAlpha,
 			 srcWidth, srcHeight, scaledWidth, scaledHeight);
-  
+
   if (scaledImg == NULL) {
     return splashErrBadArg;
   }
@@ -3802,7 +3954,7 @@ SplashBitmap *Splash::scaleImage(SplashImageSource src, void *srcData,
 				 int scaledWidth, int scaledHeight) {
   SplashBitmap *dest;
 
-  dest = new SplashBitmap(scaledWidth, scaledHeight, 1, srcMode, srcAlpha);
+  dest = new SplashBitmap(scaledWidth, scaledHeight, 1, srcMode, srcAlpha, gTrue, bitmap->getSeparationList());
   if (dest->getDataPtr() != NULL) {
     if (scaledHeight < srcHeight) {
       if (scaledWidth < srcWidth) {
@@ -3838,6 +3990,7 @@ void Splash::scaleImageYdXd(SplashImageSource src, void *srcData,
   Guint pix0, pix1, pix2;
 #if SPLASH_CMYK
   Guint pix3;
+  Guint pix[SPOT_NCOMPS+4], cp;
 #endif
   Guint alpha;
   Guchar *destPtr, *destAlphaPtr;
@@ -4017,6 +4170,25 @@ void Splash::scaleImageYdXd(SplashImageSource src, void *srcData,
 	*destPtr++ = (Guchar)pix2;
 	*destPtr++ = (Guchar)pix3;
 	break;
+      case splashModeDeviceN8:
+
+	// compute the final pixel
+  for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    pix[cp] = 0;
+	for (i = 0; i < xStep; ++i) {
+    for (cp = 0; cp < SPOT_NCOMPS+4; cp++) {
+      pix[cp] += pixBuf[xx + cp];
+    }
+    xx += (SPOT_NCOMPS+4);
+	}
+	// pix / xStep * yStep
+  for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    pix[cp] = (pix[cp] * d) >> 23;
+
+	// store the pixel
+  for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    *destPtr++ = (Guchar)pix[cp];
+	break;
 #endif
 
 
@@ -4168,6 +4340,12 @@ void Splash::scaleImageYdXu(SplashImageSource src, void *srcData,
 	  *destPtr++ = (Guchar)pix[3];
 	}
 	break;
+      case splashModeDeviceN8:
+	for (i = 0; i < xStep; ++i) {
+    for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      *destPtr++ = (Guchar)pix[cp];
+	}
+	break;
 #endif
       }
 
@@ -4311,6 +4489,13 @@ void Splash::scaleImageYuXd(SplashImageSource src, void *srcData,
 	  *destPtr++ = (Guchar)pix[3];
 	}
 	break;
+      case splashModeDeviceN8:
+	for (i = 0; i < yStep; ++i) {
+	  destPtr = destPtr0 + (i * scaledWidth + x) * nComps;
+    for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      *destPtr++ = (Guchar)pix[cp];
+	}
+	break;
 #endif
       }
 
@@ -4459,6 +4644,15 @@ void Splash::scaleImageYuXu(SplashImageSource src, void *srcData,
 	  }
 	}
 	break;
+      case splashModeDeviceN8:
+	for (i = 0; i < yStep; ++i) {
+	  for (j = 0; j < xStep; ++j) {
+	    destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps;
+      for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+        *destPtr++ = (Guchar)pix[cp];
+	  }
+	}
+	break;
 #endif
       }
 
@@ -4681,6 +4875,10 @@ SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
     return splashErrModeMismatch;
   }
 
+  if(src->getSeparationList()->getLength() > bitmap->getSeparationList()->getLength()) {
+    for (x = bitmap->getSeparationList()->getLength(); x < src->getSeparationList()->getLength(); x++)
+      bitmap->getSeparationList()->append(((GfxSeparationColorSpace *)src->getSeparationList()->get(x))->copy());
+  }
   if (src->alpha) {
     pipeInit(&pipe, xDest, yDest, NULL, pixel,
 	     (Guchar)splashRound(state->fillAlpha * 255), gTrue, nonIsolated,
@@ -4694,7 +4892,7 @@ SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
 	  alpha = *ap++;
 	  // this uses shape instead of alpha, which isn't technically
 	  // correct, but works out the same
-	  pipe.shape = alpha;
+    pipe.shape = alpha;
 	  (this->*pipe.run)(&pipe);
 	}
       }
@@ -4712,7 +4910,7 @@ SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
 	  if (state->clip->test(xDest + x, yDest + y)) {
 	    // this uses shape instead of alpha, which isn't technically
 	    // correct, but works out the same
-	    pipe.shape = alpha;
+      pipe.shape = alpha;
 	    (this->*pipe.run)(&pipe);
 	    updateModX(xDest + x);
 	    updateModY(yDest + y);
@@ -4763,6 +4961,7 @@ void Splash::compositeBackground(SplashColorPtr color) {
   Guchar alpha, alpha1, c, color0, color1, color2;
 #if SPLASH_CMYK
   Guchar color3;
+  Guchar colorsp[SPOT_NCOMPS+4], cp;
 #endif
   int x, y, mask;
 
@@ -4892,6 +5091,29 @@ void Splash::compositeBackground(SplashColorPtr color) {
       }
     }
     break;
+  case splashModeDeviceN8:
+    for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      colorsp[cp] = color[cp];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	if (alpha == 0)
+	{
+    for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      p[cp] = colorsp[cp];
+	}
+	else if (alpha != 255)
+	{
+	  alpha1 = 255 - alpha;
+    for (cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      p[cp] = div255(alpha1 * colorsp[cp] + alpha * p[cp]);
+	}
+	p += (SPOT_NCOMPS+4);
+      }
+    }
+    break;
 #endif
   }
   memset(bitmap->alpha, 255, bitmap->width * bitmap->height);
@@ -4951,6 +5173,9 @@ GBool Splash::gouraudTriangleShadedFill(SplashGouraudColor *shading)
     case splashModeCMYK8:
       colorComps=4;
     break;
+    case splashModeDeviceN8:
+      colorComps=SPOT_NCOMPS+4;
+    break;
 #endif
   }
 
@@ -5289,6 +5514,16 @@ SplashError Splash::blitTransparent(SplashBitmap *src, int xSrc, int ySrc,
       }
     }
     break;
+  case splashModeDeviceN8:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + (SPOT_NCOMPS+4) * xDest];
+      sp = &src->data[(ySrc + y) * src->rowSize + (SPOT_NCOMPS+4) * xSrc];
+      for (x = 0; x < w; ++x) {
+        for (int cp=0; cp < SPOT_NCOMPS+4; cp++)
+          *p++ = *sp++;
+      }
+    }
+    break;
 #endif
   }
 
diff --git a/splash/Splash.h b/splash/Splash.h
index f4fb542..223afdd 100644
--- a/splash/Splash.h
+++ b/splash/Splash.h
@@ -60,6 +60,7 @@ typedef GBool (*SplashImageSource)(void *data, SplashColorPtr colorLine,
 enum SplashPipeResultColorCtrl {
 #if SPLASH_CMYK
   splashPipeResultColorNoAlphaBlendCMYK,
+  splashPipeResultColorNoAlphaBlendDeviceN,
 #endif
   splashPipeResultColorNoAlphaBlendRGB,
   splashPipeResultColorNoAlphaBlendMono,
@@ -67,12 +68,14 @@ enum SplashPipeResultColorCtrl {
   splashPipeResultColorAlphaNoBlendRGB,
 #if SPLASH_CMYK
   splashPipeResultColorAlphaNoBlendCMYK,
+  splashPipeResultColorAlphaNoBlendDeviceN,
 #endif
   splashPipeResultColorAlphaBlendMono,
   splashPipeResultColorAlphaBlendRGB
 #if SPLASH_CMYK
   ,
-  splashPipeResultColorAlphaBlendCMYK
+  splashPipeResultColorAlphaBlendCMYK,
+  splashPipeResultColorAlphaBlendDeviceN
 #endif
 };
 
@@ -285,6 +288,7 @@ private:
   void pipeRunSimpleBGR8(SplashPipe *pipe);
 #if SPLASH_CMYK
   void pipeRunSimpleCMYK8(SplashPipe *pipe);
+  void pipeRunSimpleDeviceN8(SplashPipe *pipe);
 #endif
   void pipeRunAAMono1(SplashPipe *pipe);
   void pipeRunAAMono8(SplashPipe *pipe);
@@ -293,6 +297,7 @@ private:
   void pipeRunAABGR8(SplashPipe *pipe);
 #if SPLASH_CMYK
   void pipeRunAACMYK8(SplashPipe *pipe);
+  void pipeRunAADeviceN8(SplashPipe *pipe);
 #endif
   void pipeSetXY(SplashPipe *pipe, int x, int y);
   void pipeIncX(SplashPipe *pipe);
diff --git a/splash/SplashBitmap.cc b/splash/SplashBitmap.cc
index cd85543..5fe61a2 100644
--- a/splash/SplashBitmap.cc
+++ b/splash/SplashBitmap.cc
@@ -45,6 +45,7 @@
 #include "goo/PNGWriter.h"
 #include "goo/TiffWriter.h"
 #include "goo/ImgWriter.h"
+#include "goo/GooList.h"
 
 //------------------------------------------------------------------------
 // SplashBitmap
@@ -52,7 +53,7 @@
 
 SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPadA,
 			   SplashColorMode modeA, GBool alphaA,
-			   GBool topDown) {
+			   GBool topDown, GooList *separationListA) {
   width = widthA;
   height = heightA;
   mode = modeA;
@@ -95,6 +96,13 @@ SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPadA,
       rowSize = -1;
     }
     break;
+  case splashModeDeviceN8:
+    if (width > 0 && width <= INT_MAX / 4) {
+      rowSize = width * (SPOT_NCOMPS + 4);
+    } else {
+      rowSize = -1;
+    }
+    break;
 #endif
   }
   if (rowSize > 0) {
@@ -115,11 +123,15 @@ SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPadA,
   } else {
     alpha = NULL;
   }
+  separationList = new GooList();
+  if (separationListA != NULL)
+    for (int i = 0; i < separationListA->getLength(); i++)
+      separationList->append(((GfxSeparationColorSpace *) separationListA->get(i))->copy());
 }
 
 SplashBitmap *SplashBitmap::copy(SplashBitmap *src) {
   SplashBitmap *result = new SplashBitmap(src->getWidth(), src->getHeight(), src->getRowPad(), 
-        src->getMode(), src->getAlphaPtr() != NULL, src->getRowSize() >= 0);
+    src->getMode(), src->getAlphaPtr() != NULL, src->getRowSize() >= 0, src->getSeparationList());
   Guchar *dataSource = src->getDataPtr();
   Guchar *dataDest = result->getDataPtr();
   int amount = src->getRowSize();
@@ -146,6 +158,7 @@ SplashBitmap::~SplashBitmap() {
     }
   }
   gfree(alpha);
+  deleteGooList(separationList, GfxSeparationColorSpace);
 }
 
 
@@ -234,6 +247,7 @@ SplashError SplashBitmap::writePNMFile(FILE *f) {
 
 #if SPLASH_CMYK
   case splashModeCMYK8:
+  case splashModeDeviceN8:
     // PNM doesn't support CMYK
     error(errInternal, -1, "unsupported SplashBitmap mode");
     return splashErrGeneric;
@@ -300,6 +314,11 @@ void SplashBitmap::getPixel(int x, int y, SplashColorPtr pixel) {
     pixel[2] = p[2];
     pixel[3] = p[3];
     break;
+  case splashModeDeviceN8:
+    p = &data[y * rowSize + (SPOT_NCOMPS + 4) * x];
+    for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++)
+      pixel[cp] = p[cp];
+    break;
 #endif
   }
 }
@@ -386,6 +405,31 @@ void SplashBitmap::getRGBLine(int yl, SplashColorPtr line) {
     m = byteToDbl(col[1]);
     y = byteToDbl(col[2]);
     k = byteToDbl(col[3]);
+#if SPLASH_CMYK
+    if (separationList->getLength() > 0) {
+      for (int i = 0; i < separationList->getLength(); i++) {
+        if (col[i+4] > 0) {
+          GfxCMYK cmyk;
+          GfxColor input;
+          input.c[0] = byteToCol(col[i+4]);
+          GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)separationList->get(i);
+          sepCS->getCMYK(&input, &cmyk);
+          col[0] = colToByte(cmyk.c);
+          col[1] = colToByte(cmyk.m);
+          col[2] = colToByte(cmyk.y);
+          col[3] = colToByte(cmyk.k);
+          c += byteToDbl(col[0]);
+          m += byteToDbl(col[1]);
+          y += byteToDbl(col[2]);
+          k += byteToDbl(col[3]);
+        }
+      }
+      if (c > 1) c = 1;
+      if (m > 1) m = 1;
+      if (y > 1) y = 1;
+      if (k > 1) k = 1;
+    }
+#endif
     c1 = 1 - c;
     m1 = 1 - m;
     y1 = 1 - y;
@@ -397,10 +441,52 @@ void SplashBitmap::getRGBLine(int yl, SplashColorPtr line) {
   }
 }
 
+#if SPLASH_CMYK
+void SplashBitmap::getCMYKLine(int yl, SplashColorPtr line) {
+  SplashColor col;
+
+  for (int x = 0; x < width; x++) {
+    getPixel(x, yl, col);
+    if (separationList->getLength() > 0) {
+      double c, m, y, k;
+      c = byteToDbl(col[0]);
+      m = byteToDbl(col[1]);
+      y = byteToDbl(col[2]);
+      k = byteToDbl(col[3]);
+      for (int i = 0; i < separationList->getLength(); i++) {
+        if (col[i+4] > 0) {
+          GfxCMYK cmyk;
+          GfxColor input;
+          input.c[0] = byteToCol(col[i+4]);
+          GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)separationList->get(i);
+          sepCS->getCMYK(&input, &cmyk);
+          col[0] = colToByte(cmyk.c);
+          col[1] = colToByte(cmyk.m);
+          col[2] = colToByte(cmyk.y);
+          col[3] = colToByte(cmyk.k);
+          c += byteToDbl(col[0]);
+          m += byteToDbl(col[1]);
+          y += byteToDbl(col[2]);
+          k += byteToDbl(col[3]);
+        }
+      }
+      col[0] = dblToByte(clip01(c));
+      col[1] = dblToByte(clip01(m));
+      col[2] = dblToByte(clip01(y));
+      col[3] = dblToByte(clip01(k));
+    }
+    *line++ = col[0];
+    *line++ = col[1];
+    *line++ = col[2];
+    *line++ = col[3];
+  }
+}
+#endif
+
 SplashError SplashBitmap::writeImgFile(ImgWriter *writer, FILE *f, int hDPI, int vDPI) {
   if (mode != splashModeRGB8 && mode != splashModeMono8 && mode != splashModeMono1 && mode != splashModeXBGR8
 #if SPLASH_CMYK
-      && mode != splashModeCMYK8
+      && mode != splashModeCMYK8 && mode != splashModeDeviceN8
 #endif
      ) {
     error(errInternal, -1, "unsupported SplashBitmap mode");
@@ -440,6 +526,29 @@ SplashError SplashBitmap::writeImgFile(ImgWriter *writer, FILE *f, int hDPI, int
         delete[] row;
       }
     break;
+    case splashModeDeviceN8:
+      if (writer->supportCMYK()) {
+        unsigned char *row = new unsigned char[4 * width];
+        for (int y = 0; y < height; y++) {
+          getCMYKLine(y, row);
+          if (!writer->writeRow(&row)) {
+            delete[] row;
+            return splashErrGeneric;
+          }
+        }
+        delete[] row;
+      } else {
+        unsigned char *row = new unsigned char[3 * width];
+        for (int y = 0; y < height; y++) {
+          getRGBLine(y, row);
+          if (!writer->writeRow(&row)) {
+            delete[] row;
+            return splashErrGeneric;
+          }
+        }
+        delete[] row;
+      }
+    break;
 #endif
     case splashModeRGB8:
     {
diff --git a/splash/SplashBitmap.h b/splash/SplashBitmap.h
index 5ef5573..0bff205 100644
--- a/splash/SplashBitmap.h
+++ b/splash/SplashBitmap.h
@@ -34,6 +34,7 @@
 #endif
 
 #include "SplashTypes.h"
+#include "poppler/GfxState.h"
 #include <stdio.h>
 
 class ImgWriter;
@@ -51,7 +52,7 @@ public:
   // upside-down, i.e., with the last row first in memory.
   SplashBitmap(int widthA, int heightA, int rowPad,
 	       SplashColorMode modeA, GBool alphaA,
-	       GBool topDown = gTrue);
+	       GBool topDown = gTrue, GooList *separationList = NULL);
   static SplashBitmap *copy(SplashBitmap *src);
 
   ~SplashBitmap();
@@ -64,6 +65,7 @@ public:
   SplashColorMode getMode() { return mode; }
   SplashColorPtr getDataPtr() { return data; }
   Guchar *getAlphaPtr() { return alpha; }
+  GooList *getSeparationList() { return separationList; }
 
   SplashError writePNMFile(char *fileName);
   SplashError writePNMFile(FILE *f);
@@ -75,6 +77,9 @@ public:
 
   void getPixel(int x, int y, SplashColorPtr pixel);
   void getRGBLine(int y, SplashColorPtr line);
+#if SPLASH_CMYK
+  void getCMYKLine(int y, SplashColorPtr line);
+#endif
   Guchar getAlpha(int x, int y);
 
   // Caller takes ownership of the bitmap data.  The SplashBitmap
@@ -92,6 +97,7 @@ private:
   SplashColorPtr data;		// pointer to row zero of the color data
   Guchar *alpha;		// pointer to row zero of the alpha data
 				//   (always top-down)
+  GooList *separationList; // list of spot colorants and their mapping functions
 
   friend class Splash;
 };
diff --git a/splash/SplashState.cc b/splash/SplashState.cc
index e258f66..fd2789d 100644
--- a/splash/SplashState.cc
+++ b/splash/SplashState.cc
@@ -40,7 +40,7 @@
 int splashColorModeNComps[] = {
   1, 1, 3, 3, 4
 #if SPLASH_CMYK
-  ,4
+  , 4, 4 + SPOT_NCOMPS
 #endif
 };
 
@@ -80,10 +80,14 @@ SplashState::SplashState(int width, int height, GBool vectorAntialias,
     rgbTransferG[i] = (Guchar)i;
     rgbTransferB[i] = (Guchar)i;
     grayTransfer[i] = (Guchar)i;
+#if SPLASH_CMYK
     cmykTransferC[i] = (Guchar)i;
     cmykTransferM[i] = (Guchar)i;
     cmykTransferY[i] = (Guchar)i;
     cmykTransferK[i] = (Guchar)i;
+    for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      deviceNTransfer[cp][i] = (Guchar)i;
+#endif
   }
   overprintMask = 0xffffffff;
   overprintAdditive = gFalse;
@@ -126,10 +130,14 @@ SplashState::SplashState(int width, int height, GBool vectorAntialias,
     rgbTransferG[i] = (Guchar)i;
     rgbTransferB[i] = (Guchar)i;
     grayTransfer[i] = (Guchar)i;
+#if SPLASH_CMYK
     cmykTransferC[i] = (Guchar)i;
     cmykTransferM[i] = (Guchar)i;
     cmykTransferY[i] = (Guchar)i;
     cmykTransferK[i] = (Guchar)i;
+    for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      deviceNTransfer[cp][i] = (Guchar)i;
+#endif
   }
   overprintMask = 0xffffffff;
   overprintAdditive = gFalse;
@@ -170,10 +178,14 @@ SplashState::SplashState(SplashState *state) {
   memcpy(rgbTransferG, state->rgbTransferG, 256);
   memcpy(rgbTransferB, state->rgbTransferB, 256);
   memcpy(grayTransfer, state->grayTransfer, 256);
+#if SPLASH_CMYK
   memcpy(cmykTransferC, state->cmykTransferC, 256);
   memcpy(cmykTransferM, state->cmykTransferM, 256);
   memcpy(cmykTransferY, state->cmykTransferY, 256);
   memcpy(cmykTransferK, state->cmykTransferK, 256);
+  for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+    memcpy(deviceNTransfer[cp], state->deviceNTransfer[cp], 256);
+#endif
   overprintMask = state->overprintMask;
   overprintAdditive = state->overprintAdditive;
   next = NULL;
@@ -228,16 +240,24 @@ void SplashState::setSoftMask(SplashBitmap *softMaskA) {
 
 void SplashState::setTransfer(Guchar *red, Guchar *green, Guchar *blue,
 			      Guchar *gray) {
+#if SPLASH_CMYK
   int i;
 
-  memcpy(rgbTransferR, red, 256);
-  memcpy(rgbTransferG, green, 256);
-  memcpy(rgbTransferB, blue, 256);
-  memcpy(grayTransfer, gray, 256);
   for (i = 0; i < 256; ++i) {
     cmykTransferC[i] = 255 - rgbTransferR[255 - i];
     cmykTransferM[i] = 255 - rgbTransferG[255 - i];
     cmykTransferY[i] = 255 - rgbTransferB[255 - i];
     cmykTransferK[i] = 255 - grayTransfer[255 - i];
   }
+  for (i = 0; i < 256; ++i) {
+    deviceNTransfer[0][i] = 255 - rgbTransferR[255 - i];
+    deviceNTransfer[1][i] = 255 - rgbTransferG[255 - i];
+    deviceNTransfer[2][i] = 255 - rgbTransferB[255 - i];
+    deviceNTransfer[3][i] = 255 - grayTransfer[255 - i];
+  }
+#endif
+  memcpy(rgbTransferR, red, 256);
+  memcpy(rgbTransferG, green, 256);
+  memcpy(rgbTransferB, blue, 256);
+  memcpy(grayTransfer, gray, 256);
 }
diff --git a/splash/SplashState.h b/splash/SplashState.h
index 01e7772..13d5478 100644
--- a/splash/SplashState.h
+++ b/splash/SplashState.h
@@ -121,10 +121,13 @@ private:
          rgbTransferG[256],
          rgbTransferB[256];
   Guchar grayTransfer[256];
+#if SPLASH_CMYK
   Guchar cmykTransferC[256],
          cmykTransferM[256],
          cmykTransferY[256],
          cmykTransferK[256];
+  Guchar deviceNTransfer[SPOT_NCOMPS+4][256];
+#endif
   Guint overprintMask;
   GBool overprintAdditive;
 
diff --git a/splash/SplashTypes.h b/splash/SplashTypes.h
index 65525b1..531b945 100644
--- a/splash/SplashTypes.h
+++ b/splash/SplashTypes.h
@@ -13,7 +13,7 @@
 //
 // Copyright (C) 2006, 2010 Albert Astals Cid <aacid at kde.org>
 // Copyright (C) 2008 Tomas Are Haavet <tomasare at gmail.com>
-// Copyright (C) 2009, 2011 Thomas Freitag <Thomas.Freitag at alfa.de>
+// Copyright (C) 2009, 2011, 2012 Thomas Freitag <Thomas.Freitag at alfa.de>
 // Copyright (C) 2009 Stefan Thomas <thomas at eload24.com>
 // Copyright (C) 2010 William Bader <williambader at hotmail.com>
 //
@@ -46,6 +46,12 @@ typedef double SplashCoord;
 
 #define splashAASize 4
 
+#ifdef SPLASH_CMYK
+#ifndef SPOT_NCOMPS
+#define SPOT_NCOMPS 4
+#endif
+#endif
+
 //------------------------------------------------------------------------
 // colors
 //------------------------------------------------------------------------
@@ -62,8 +68,11 @@ enum SplashColorMode {
 				//   XBGRXBGR...
 #if SPLASH_CMYK
   ,
-  splashModeCMYK8		// 1 byte per component, 4 bytes per pixel:
+  splashModeCMYK8,	// 1 byte per component, 4 bytes per pixel:
 				//   CMYKCMYK...
+  splashModeDeviceN8		// 1 byte per component, 
+                        // 4 bytes + n bytes spot colors per pixel:
+				                // CMYKSSSSCMYKSSSS...
 #endif
 };
 
@@ -72,7 +81,11 @@ enum SplashColorMode {
 extern int splashColorModeNComps[];
 
 // max number of components in any SplashColor
+#if SPLASH_CMYK
+#define splashMaxColorComps SPOT_NCOMPS+4
+#else
 #define splashMaxColorComps 4
+#endif
 
 typedef Guchar SplashColor[splashMaxColorComps];
 typedef Guchar *SplashColorPtr;
@@ -93,6 +106,13 @@ static inline Guchar splashCMYK8C(SplashColorPtr cmyk8) { return cmyk8[0]; }
 static inline Guchar splashCMYK8M(SplashColorPtr cmyk8) { return cmyk8[1]; }
 static inline Guchar splashCMYK8Y(SplashColorPtr cmyk8) { return cmyk8[2]; }
 static inline Guchar splashCMYK8K(SplashColorPtr cmyk8) { return cmyk8[3]; }
+
+// DEVICEN8
+static inline Guchar splashDeviceN8C(SplashColorPtr deviceN8) { return deviceN8[0]; }
+static inline Guchar splashDeviceN8M(SplashColorPtr deviceN8) { return deviceN8[1]; }
+static inline Guchar splashDeviceN8Y(SplashColorPtr deviceN8) { return deviceN8[2]; }
+static inline Guchar splashDeviceN8K(SplashColorPtr deviceN8) { return deviceN8[3]; }
+static inline Guchar splashDeviceN8S(SplashColorPtr deviceN8, int nSpot) { return deviceN8[4 + nSpot]; }
 #endif
 
 static inline void splashClearColor(SplashColorPtr dest) {
@@ -101,6 +121,8 @@ static inline void splashClearColor(SplashColorPtr dest) {
   dest[2] = 0;
 #if SPLASH_CMYK
   dest[3] = 0;
+  for (int i = SPOT_NCOMPS; i < SPOT_NCOMPS + 4; i++)
+    dest[i] = 0;
 #endif
 }
 
@@ -110,6 +132,8 @@ static inline void splashColorCopy(SplashColorPtr dest, SplashColorPtr src) {
   dest[2] = src[2];
 #if SPLASH_CMYK
   dest[3] = src[3];
+  for (int i = SPOT_NCOMPS; i < SPOT_NCOMPS + 4; i++)
+    dest[i] = src[i];
 #endif
 }
 
@@ -119,6 +143,8 @@ static inline void splashColorXor(SplashColorPtr dest, SplashColorPtr src) {
   dest[2] ^= src[2];
 #if SPLASH_CMYK
   dest[3] ^= src[3];
+  for (int i = SPOT_NCOMPS; i < SPOT_NCOMPS + 4; i++)
+    dest[i] ^= src[i];
 #endif
 }
 
diff --git a/utils/pdftoppm.cc b/utils/pdftoppm.cc
index 04a0dfb..ec10c4a 100644
--- a/utils/pdftoppm.cc
+++ b/utils/pdftoppm.cc
@@ -1,440 +1,438 @@
-//========================================================================
-//
-// pdftoppm.cc
-//
-// Copyright 2003 Glyph & Cog, LLC
-//
-//========================================================================
-
-//========================================================================
-//
-// Modified under the Poppler project - http://poppler.freedesktop.org
-//
-// All changes made under the Poppler project to this file are licensed
-// under GPL version 2 or later
-//
-// Copyright (C) 2007 Ilmari Heikkinen <ilmari.heikkinen at gmail.com>
-// Copyright (C) 2008 Richard Airlie <richard.airlie at maglabs.net>
-// Copyright (C) 2009 Michael K. Johnson <a1237 at danlj.org>
-// Copyright (C) 2009 Shen Liang <shenzhuxi at gmail.com>
-// Copyright (C) 2009 Stefan Thomas <thomas at eload24.com>
-// Copyright (C) 2009-2011 Albert Astals Cid <aacid at kde.org>
-// Copyright (C) 2010, 2012 Adrian Johnson <ajohnson at redneon.com>
-// Copyright (C) 2010 Hib Eris <hib at hiberis.nl>
-// Copyright (C) 2010 Jonathan Liu <net147 at gmail.com>
-// Copyright (C) 2010 William Bader <williambader at hotmail.com>
-// Copyright (C) 2011 Thomas Freitag <Thomas.Freitag at alfa.de>
-//
-// To see a description of the changes please see the Changelog file that
-// came with your tarball or type make ChangeLog if you are building from git
-//
-//========================================================================
-
-#include "config.h"
-#include <poppler-config.h>
-#ifdef _WIN32
-#include <fcntl.h> // for O_BINARY
-#include <io.h>    // for setmode
-#endif
-#include <stdio.h>
-#include <math.h>
-#include "parseargs.h"
-#include "goo/gmem.h"
-#include "goo/GooString.h"
-#include "GlobalParams.h"
-#include "Object.h"
-#include "PDFDoc.h"
-#include "PDFDocFactory.h"
-#include "splash/SplashBitmap.h"
-#include "splash/Splash.h"
-#include "SplashOutputDev.h"
-
-static int firstPage = 1;
-static int lastPage = 0;
-static GBool printOnlyOdd = gFalse;
-static GBool printOnlyEven = gFalse;
-static GBool singleFile = gFalse;
-static double resolution = 0.0;
-static double x_resolution = 150.0;
-static double y_resolution = 150.0;
-static int scaleTo = 0;
-static int x_scaleTo = 0;
-static int y_scaleTo = 0;
-static int x = 0;
-static int y = 0;
-static int w = 0;
-static int h = 0;
-static int sz = 0;
-static GBool useCropBox = gFalse;
-static GBool mono = gFalse;
-static GBool gray = gFalse;
-static GBool png = gFalse;
-static GBool jpeg = gFalse;
-static GBool jpegcmyk = gFalse;
-static GBool tiff = gFalse;
-#if SPLASH_CMYK
-static GBool overprint = gFalse;
-#endif
-static char enableFreeTypeStr[16] = "";
-static char antialiasStr[16] = "";
-static char vectorAntialiasStr[16] = "";
-static char ownerPassword[33] = "";
-static char userPassword[33] = "";
-static char TiffCompressionStr[16] = "";
-static GBool quiet = gFalse;
-static GBool printVersion = gFalse;
-static GBool printHelp = gFalse;
-
-static const ArgDesc argDesc[] = {
-  {"-f",      argInt,      &firstPage,     0,
-   "first page to print"},
-  {"-l",      argInt,      &lastPage,      0,
-   "last page to print"},
-  {"-o",      argFlag,      &printOnlyOdd, 0,
-   "print only odd pages"},
-  {"-e",      argFlag,      &printOnlyEven, 0,
-   "print only even pages"},
-  {"-singlefile", argFlag,  &singleFile,   0,
-   "write only the first page and do not add digits"},
-
-  {"-r",      argFP,       &resolution,    0,
-   "resolution, in DPI (default is 150)"},
-  {"-rx",      argFP,       &x_resolution,    0,
-   "X resolution, in DPI (default is 150)"},
-  {"-ry",      argFP,       &y_resolution,    0,
-   "Y resolution, in DPI (default is 150)"},
-  {"-scale-to",      argInt,       &scaleTo,    0,
-   "scales each page to fit within scale-to*scale-to pixel box"},
-  {"-scale-to-x",      argInt,       &x_scaleTo,    0,
-   "scales each page horizontally to fit in scale-to-x pixels"},
-  {"-scale-to-y",      argInt,       &y_scaleTo,    0,
-   "scales each page vertically to fit in scale-to-y pixels"},
-
-  {"-x",      argInt,      &x,             0,
-   "x-coordinate of the crop area top left corner"},
-  {"-y",      argInt,      &y,             0,
-   "y-coordinate of the crop area top left corner"},
-  {"-W",      argInt,      &w,             0,
-   "width of crop area in pixels (default is 0)"},
-  {"-H",      argInt,      &h,             0,
-   "height of crop area in pixels (default is 0)"},
-  {"-sz",     argInt,      &sz,            0,
-   "size of crop square in pixels (sets W and H)"},
-  {"-cropbox",argFlag,     &useCropBox,    0,
-   "use the crop box rather than media box"},
-
-  {"-mono",   argFlag,     &mono,          0,
-   "generate a monochrome PBM file"},
-  {"-gray",   argFlag,     &gray,          0,
-   "generate a grayscale PGM file"},
-#if ENABLE_LIBPNG
-  {"-png",    argFlag,     &png,           0,
-   "generate a PNG file"},
-#endif
-#if ENABLE_LIBJPEG
-  {"-jpeg",   argFlag,     &jpeg,           0,
-   "generate a JPEG file"},
-#if SPLASH_CMYK
-  {"-jpegcmyk",argFlag,    &jpegcmyk,       0,
-   "generate a CMYK JPEG file"},
-#endif
-#endif
-#if SPLASH_CMYK
-  {"-overprint",argFlag,   &overprint,      0,
-   "enable overprint"},
-#endif
-#if ENABLE_LIBTIFF
-  {"-tiff",    argFlag,     &tiff,           0,
-   "generate a TIFF file"},
-  {"-tiffcompression", argString, TiffCompressionStr, sizeof(TiffCompressionStr),
-   "set TIFF compression: none, packbits, jpeg, lzw, deflate"},
-#endif
-#if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H
-  {"-freetype",   argString,      enableFreeTypeStr, sizeof(enableFreeTypeStr),
-   "enable FreeType font rasterizer: yes, no"},
-#endif
-  
-  {"-aa",         argString,      antialiasStr,   sizeof(antialiasStr),
-   "enable font anti-aliasing: yes, no"},
-  {"-aaVector",   argString,      vectorAntialiasStr, sizeof(vectorAntialiasStr),
-   "enable vector anti-aliasing: yes, no"},
-  
-  {"-opw",    argString,   ownerPassword,  sizeof(ownerPassword),
-   "owner password (for encrypted files)"},
-  {"-upw",    argString,   userPassword,   sizeof(userPassword),
-   "user password (for encrypted files)"},
-  
-  {"-q",      argFlag,     &quiet,         0,
-   "don't print any messages or errors"},
-  {"-v",      argFlag,     &printVersion,  0,
-   "print copyright and version info"},
-  {"-h",      argFlag,     &printHelp,     0,
-   "print usage information"},
-  {"-help",   argFlag,     &printHelp,     0,
-   "print usage information"},
-  {"--help",  argFlag,     &printHelp,     0,
-   "print usage information"},
-  {"-?",      argFlag,     &printHelp,     0,
-   "print usage information"},
-  {NULL}
-};
-
-static void savePageSlice(PDFDoc *doc,
-                   SplashOutputDev *splashOut, 
-                   int pg, int x, int y, int w, int h, 
-                   double pg_w, double pg_h, 
-                   char *ppmFile) {
-  if (w == 0) w = (int)ceil(pg_w);
-  if (h == 0) h = (int)ceil(pg_h);
-  w = (x+w > pg_w ? (int)ceil(pg_w-x) : w);
-  h = (y+h > pg_h ? (int)ceil(pg_h-y) : h);
-  doc->displayPageSlice(splashOut, 
-    pg, x_resolution, y_resolution, 
-    0,
-    !useCropBox, gFalse, gFalse,
-    x, y, w, h
-  );
-
-  SplashBitmap *bitmap = splashOut->getBitmap();
-  
-  if (ppmFile != NULL) {
-    if (png) {
-      bitmap->writeImgFile(splashFormatPng, ppmFile, x_resolution, y_resolution);
-    } else if (jpeg) {
-      bitmap->writeImgFile(splashFormatJpeg, ppmFile, x_resolution, y_resolution);
-    } else if (jpegcmyk) {
-      bitmap->writeImgFile(splashFormatJpegCMYK, ppmFile, x_resolution, y_resolution);
-    } else if (tiff) {
-      bitmap->writeImgFile(splashFormatTiff, ppmFile, x_resolution, y_resolution, TiffCompressionStr);
-    } else {
-      bitmap->writePNMFile(ppmFile);
-    }
-  } else {
-#ifdef _WIN32
-    setmode(fileno(stdout), O_BINARY);
-#endif
-
-    if (png) {
-      bitmap->writeImgFile(splashFormatPng, stdout, x_resolution, y_resolution);
-    } else if (jpeg) {
-      bitmap->writeImgFile(splashFormatJpeg, stdout, x_resolution, y_resolution);
-    } else if (tiff) {
-      bitmap->writeImgFile(splashFormatTiff, stdout, x_resolution, y_resolution, TiffCompressionStr);
-    } else {
-      bitmap->writePNMFile(stdout);
-    }
-  }
-}
-
-static int numberOfCharacters(unsigned int n)
-{
-  int charNum = 0;
-  while (n >= 10)
-  {
-    n = n / 10;
-    charNum++;
-  }
-  charNum++;
-  return charNum;
-}
-
-int main(int argc, char *argv[]) {
-  PDFDoc *doc;
-  GooString *fileName = NULL;
-  char *ppmRoot = NULL;
-  char *ppmFile;
-  GooString *ownerPW, *userPW;
-  SplashColor paperColor;
-  SplashOutputDev *splashOut;
-  GBool ok;
-  int exitCode;
-  int pg, pg_num_len;
-  double pg_w, pg_h, tmp;
-
-  exitCode = 99;
-
-  // parse args
-  ok = parseArgs(argDesc, &argc, argv);
-  if (mono && gray) {
-    ok = gFalse;
-  }
-  if ( resolution != 0.0 &&
-       (x_resolution == 150.0 ||
-        y_resolution == 150.0)) {
-    x_resolution = resolution;
-    y_resolution = resolution;
-  }
-  if (!ok || argc > 3 || printVersion || printHelp) {
-    fprintf(stderr, "pdftoppm version %s\n", PACKAGE_VERSION);
-    fprintf(stderr, "%s\n", popplerCopyright);
-    fprintf(stderr, "%s\n", xpdfCopyright);
-    if (!printVersion) {
-      printUsage("pdftoppm", "[PDF-file [PPM-file-prefix]]", argDesc);
-    }
-    if (printVersion || printHelp)
-      exitCode = 0;
-    goto err0;
-  }
-  if (argc > 1) fileName = new GooString(argv[1]);
-  if (argc == 3) ppmRoot = argv[2];
-
-  // read config file
-  globalParams = new GlobalParams();
-  if (enableFreeTypeStr[0]) {
-    if (!globalParams->setEnableFreeType(enableFreeTypeStr)) {
-      fprintf(stderr, "Bad '-freetype' value on command line\n");
-    }
-  }
-  if (antialiasStr[0]) {
-    if (!globalParams->setAntialias(antialiasStr)) {
-      fprintf(stderr, "Bad '-aa' value on command line\n");
-    }
-  }
-  if (vectorAntialiasStr[0]) {
-    if (!globalParams->setVectorAntialias(vectorAntialiasStr)) {
-      fprintf(stderr, "Bad '-aaVector' value on command line\n");
-    }
-  }
-  if (quiet) {
-    globalParams->setErrQuiet(quiet);
-  }
-
-  // open PDF file
-  if (ownerPassword[0]) {
-    ownerPW = new GooString(ownerPassword);
-  } else {
-    ownerPW = NULL;
-  }
-  if (userPassword[0]) {
-    userPW = new GooString(userPassword);
-  } else {
-    userPW = NULL;
-  }
-
-  if (fileName == NULL) {
-    fileName = new GooString("fd://0");
-  }
-  if (fileName->cmp("-") == 0) {
-    delete fileName;
-    fileName = new GooString("fd://0");
-  }
-  doc = PDFDocFactory().createPDFDoc(*fileName, ownerPW, userPW);
-  delete fileName;
-
-  if (userPW) {
-    delete userPW;
-  }
-  if (ownerPW) {
-    delete ownerPW;
-  }
-  if (!doc->isOk()) {
-    exitCode = 1;
-    goto err1;
-  }
-
-  // get page range
-  if (firstPage < 1)
-    firstPage = 1;
-  if (singleFile && lastPage < 1)
-    lastPage = firstPage;
-  if (lastPage < 1 || lastPage > doc->getNumPages())
-    lastPage = doc->getNumPages();
-
-  if (singleFile && firstPage < lastPage) {
-    if (!quiet) {
-      fprintf(stderr,
-        "Warning: Single file will write only the first of the %d pages.\n",
-        lastPage + 1 - firstPage);
-    }
-    lastPage = firstPage;
-  }
-
-  // write PPM files
-#if SPLASH_CMYK
-  if (jpegcmyk || overprint) {
-    globalParams->setOverprintPreview(gTrue);
-    paperColor[0] = 0;
-    paperColor[1] = 0;
-    paperColor[2] = 0;
-    paperColor[3] = 0;
-  } else 
-#endif
-  {
-    paperColor[0] = 255;
-    paperColor[1] = 255;
-    paperColor[2] = 255;
-  }
-  splashOut = new SplashOutputDev(mono ? splashModeMono1 :
-				    gray ? splashModeMono8 :
-#if SPLASH_CMYK
-				    (jpegcmyk || overprint) ? splashModeCMYK8 :
-#endif
-				             splashModeRGB8, 4,
-				  gFalse, paperColor);
-  splashOut->startDoc(doc);
-  if (sz != 0) w = h = sz;
-  pg_num_len = numberOfCharacters(doc->getNumPages());
-  for (pg = firstPage; pg <= lastPage; ++pg) {
-    if (printOnlyEven && pg % 2 == 0) continue;
-    if (printOnlyOdd && pg % 2 == 1) continue;
-    if (useCropBox) {
-      pg_w = doc->getPageCropWidth(pg);
-      pg_h = doc->getPageCropHeight(pg);
-    } else {
-      pg_w = doc->getPageMediaWidth(pg);
-      pg_h = doc->getPageMediaHeight(pg);
-    }
-
-    if (scaleTo != 0) {
-      resolution = (72.0 * scaleTo) / (pg_w > pg_h ? pg_w : pg_h);
-      x_resolution = y_resolution = resolution;
-    } else {
-      if (x_scaleTo > 0) {
-        x_resolution = (72.0 * x_scaleTo) / pg_w;
-        if (y_scaleTo == -1)
-          y_resolution = x_resolution;
-      }
-      if (y_scaleTo > 0) {
-        y_resolution = (72.0 * y_scaleTo) / pg_h;
-        if (x_scaleTo == -1)
-          x_resolution = y_resolution;
-      }
-    }
-    pg_w = pg_w * (x_resolution / 72.0);
-    pg_h = pg_h * (y_resolution / 72.0);
-    if ((doc->getPageRotate(pg) == 90) || (doc->getPageRotate(pg) == 270)) {
-      tmp = pg_w;
-      pg_w = pg_h;
-      pg_h = tmp;
-    }
-    if (ppmRoot != NULL) {
-      const char *ext = png ? "png" : (jpeg || jpegcmyk) ? "jpg" : tiff ? "tif" : mono ? "pbm" : gray ? "pgm" : "ppm";
-      if (singleFile) {
-        ppmFile = new char[strlen(ppmRoot) + 1 + strlen(ext) + 1];
-        sprintf(ppmFile, "%s.%s", ppmRoot, ext);
-      } else {
-        ppmFile = new char[strlen(ppmRoot) + 1 + pg_num_len + 1 + strlen(ext) + 1];
-        sprintf(ppmFile, "%s-%0*d.%s", ppmRoot, pg_num_len, pg, ext);
-      }
-      savePageSlice(doc, splashOut, pg, x, y, w, h, pg_w, pg_h, ppmFile);
-      delete[] ppmFile;
-    } else {
-      savePageSlice(doc, splashOut, pg, x, y, w, h, pg_w, pg_h, NULL);
-    }
-  }
-  delete splashOut;
-
-  exitCode = 0;
-
-  // clean up
- err1:
-  delete doc;
-  delete globalParams;
- err0:
-
-  // check for memory leaks
-  Object::memCheck(stderr);
-  gMemReport(stderr);
-
-  return exitCode;
-}
+//========================================================================
+//
+// pdftoppm.cc
+//
+// Copyright 2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+//========================================================================
+//
+// Modified under the Poppler project - http://poppler.freedesktop.org
+//
+// All changes made under the Poppler project to this file are licensed
+// under GPL version 2 or later
+//
+// Copyright (C) 2007 Ilmari Heikkinen <ilmari.heikkinen at gmail.com>
+// Copyright (C) 2008 Richard Airlie <richard.airlie at maglabs.net>
+// Copyright (C) 2009 Michael K. Johnson <a1237 at danlj.org>
+// Copyright (C) 2009 Shen Liang <shenzhuxi at gmail.com>
+// Copyright (C) 2009 Stefan Thomas <thomas at eload24.com>
+// Copyright (C) 2009-2011 Albert Astals Cid <aacid at kde.org>
+// Copyright (C) 2010, 2012 Adrian Johnson <ajohnson at redneon.com>
+// Copyright (C) 2010 Hib Eris <hib at hiberis.nl>
+// Copyright (C) 2010 Jonathan Liu <net147 at gmail.com>
+// Copyright (C) 2010 William Bader <williambader at hotmail.com>
+// Copyright (C) 2011, 2012 Thomas Freitag <Thomas.Freitag at alfa.de>
+//
+// To see a description of the changes please see the Changelog file that
+// came with your tarball or type make ChangeLog if you are building from git
+//
+//========================================================================
+
+#include "config.h"
+#include <poppler-config.h>
+#ifdef _WIN32
+#include <fcntl.h> // for O_BINARY
+#include <io.h>    // for setmode
+#endif
+#include <stdio.h>
+#include <math.h>
+#include "parseargs.h"
+#include "goo/gmem.h"
+#include "goo/GooString.h"
+#include "GlobalParams.h"
+#include "Object.h"
+#include "PDFDoc.h"
+#include "PDFDocFactory.h"
+#include "splash/SplashBitmap.h"
+#include "splash/Splash.h"
+#include "SplashOutputDev.h"
+
+static int firstPage = 1;
+static int lastPage = 0;
+static GBool printOnlyOdd = gFalse;
+static GBool printOnlyEven = gFalse;
+static GBool singleFile = gFalse;
+static double resolution = 0.0;
+static double x_resolution = 150.0;
+static double y_resolution = 150.0;
+static int scaleTo = 0;
+static int x_scaleTo = 0;
+static int y_scaleTo = 0;
+static int x = 0;
+static int y = 0;
+static int w = 0;
+static int h = 0;
+static int sz = 0;
+static GBool useCropBox = gFalse;
+static GBool mono = gFalse;
+static GBool gray = gFalse;
+static GBool png = gFalse;
+static GBool jpeg = gFalse;
+static GBool jpegcmyk = gFalse;
+static GBool tiff = gFalse;
+#if SPLASH_CMYK
+static GBool overprint = gFalse;
+#endif
+static char enableFreeTypeStr[16] = "";
+static char antialiasStr[16] = "";
+static char vectorAntialiasStr[16] = "";
+static char ownerPassword[33] = "";
+static char userPassword[33] = "";
+static char TiffCompressionStr[16] = "";
+static GBool quiet = gFalse;
+static GBool printVersion = gFalse;
+static GBool printHelp = gFalse;
+
+static const ArgDesc argDesc[] = {
+  {"-f",      argInt,      &firstPage,     0,
+   "first page to print"},
+  {"-l",      argInt,      &lastPage,      0,
+   "last page to print"},
+  {"-o",      argFlag,      &printOnlyOdd, 0,
+   "print only odd pages"},
+  {"-e",      argFlag,      &printOnlyEven, 0,
+   "print only even pages"},
+  {"-singlefile", argFlag,  &singleFile,   0,
+   "write only the first page and do not add digits"},
+
+  {"-r",      argFP,       &resolution,    0,
+   "resolution, in DPI (default is 150)"},
+  {"-rx",      argFP,       &x_resolution,    0,
+   "X resolution, in DPI (default is 150)"},
+  {"-ry",      argFP,       &y_resolution,    0,
+   "Y resolution, in DPI (default is 150)"},
+  {"-scale-to",      argInt,       &scaleTo,    0,
+   "scales each page to fit within scale-to*scale-to pixel box"},
+  {"-scale-to-x",      argInt,       &x_scaleTo,    0,
+   "scales each page horizontally to fit in scale-to-x pixels"},
+  {"-scale-to-y",      argInt,       &y_scaleTo,    0,
+   "scales each page vertically to fit in scale-to-y pixels"},
+
+  {"-x",      argInt,      &x,             0,
+   "x-coordinate of the crop area top left corner"},
+  {"-y",      argInt,      &y,             0,
+   "y-coordinate of the crop area top left corner"},
+  {"-W",      argInt,      &w,             0,
+   "width of crop area in pixels (default is 0)"},
+  {"-H",      argInt,      &h,             0,
+   "height of crop area in pixels (default is 0)"},
+  {"-sz",     argInt,      &sz,            0,
+   "size of crop square in pixels (sets W and H)"},
+  {"-cropbox",argFlag,     &useCropBox,    0,
+   "use the crop box rather than media box"},
+
+  {"-mono",   argFlag,     &mono,          0,
+   "generate a monochrome PBM file"},
+  {"-gray",   argFlag,     &gray,          0,
+   "generate a grayscale PGM file"},
+#if ENABLE_LIBPNG
+  {"-png",    argFlag,     &png,           0,
+   "generate a PNG file"},
+#endif
+#if ENABLE_LIBJPEG
+  {"-jpeg",   argFlag,     &jpeg,           0,
+   "generate a JPEG file"},
+#if SPLASH_CMYK
+  {"-jpegcmyk",argFlag,    &jpegcmyk,       0,
+   "generate a CMYK JPEG file"},
+#endif
+#endif
+#if SPLASH_CMYK
+  {"-overprint",argFlag,   &overprint,      0,
+   "enable overprint"},
+#endif
+#if ENABLE_LIBTIFF
+  {"-tiff",    argFlag,     &tiff,           0,
+   "generate a TIFF file"},
+  {"-tiffcompression", argString, TiffCompressionStr, sizeof(TiffCompressionStr),
+   "set TIFF compression: none, packbits, jpeg, lzw, deflate"},
+#endif
+#if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H
+  {"-freetype",   argString,      enableFreeTypeStr, sizeof(enableFreeTypeStr),
+   "enable FreeType font rasterizer: yes, no"},
+#endif
+  
+  {"-aa",         argString,      antialiasStr,   sizeof(antialiasStr),
+   "enable font anti-aliasing: yes, no"},
+  {"-aaVector",   argString,      vectorAntialiasStr, sizeof(vectorAntialiasStr),
+   "enable vector anti-aliasing: yes, no"},
+  
+  {"-opw",    argString,   ownerPassword,  sizeof(ownerPassword),
+   "owner password (for encrypted files)"},
+  {"-upw",    argString,   userPassword,   sizeof(userPassword),
+   "user password (for encrypted files)"},
+  
+  {"-q",      argFlag,     &quiet,         0,
+   "don't print any messages or errors"},
+  {"-v",      argFlag,     &printVersion,  0,
+   "print copyright and version info"},
+  {"-h",      argFlag,     &printHelp,     0,
+   "print usage information"},
+  {"-help",   argFlag,     &printHelp,     0,
+   "print usage information"},
+  {"--help",  argFlag,     &printHelp,     0,
+   "print usage information"},
+  {"-?",      argFlag,     &printHelp,     0,
+   "print usage information"},
+  {NULL}
+};
+
+static void savePageSlice(PDFDoc *doc,
+                   SplashOutputDev *splashOut, 
+                   int pg, int x, int y, int w, int h, 
+                   double pg_w, double pg_h, 
+                   char *ppmFile) {
+  if (w == 0) w = (int)ceil(pg_w);
+  if (h == 0) h = (int)ceil(pg_h);
+  w = (x+w > pg_w ? (int)ceil(pg_w-x) : w);
+  h = (y+h > pg_h ? (int)ceil(pg_h-y) : h);
+  doc->displayPageSlice(splashOut, 
+    pg, x_resolution, y_resolution, 
+    0,
+    !useCropBox, gFalse, gFalse,
+    x, y, w, h
+  );
+
+  SplashBitmap *bitmap = splashOut->getBitmap();
+  
+  if (ppmFile != NULL) {
+    if (png) {
+      bitmap->writeImgFile(splashFormatPng, ppmFile, x_resolution, y_resolution);
+    } else if (jpeg) {
+      bitmap->writeImgFile(splashFormatJpeg, ppmFile, x_resolution, y_resolution);
+    } else if (jpegcmyk) {
+      bitmap->writeImgFile(splashFormatJpegCMYK, ppmFile, x_resolution, y_resolution);
+    } else if (tiff) {
+      bitmap->writeImgFile(splashFormatTiff, ppmFile, x_resolution, y_resolution, TiffCompressionStr);
+    } else {
+      bitmap->writePNMFile(ppmFile);
+    }
+  } else {
+#ifdef _WIN32
+    setmode(fileno(stdout), O_BINARY);
+#endif
+
+    if (png) {
+      bitmap->writeImgFile(splashFormatPng, stdout, x_resolution, y_resolution);
+    } else if (jpeg) {
+      bitmap->writeImgFile(splashFormatJpeg, stdout, x_resolution, y_resolution);
+    } else if (tiff) {
+      bitmap->writeImgFile(splashFormatTiff, stdout, x_resolution, y_resolution, TiffCompressionStr);
+    } else {
+      bitmap->writePNMFile(stdout);
+    }
+  }
+}
+
+static int numberOfCharacters(unsigned int n)
+{
+  int charNum = 0;
+  while (n >= 10)
+  {
+    n = n / 10;
+    charNum++;
+  }
+  charNum++;
+  return charNum;
+}
+
+int main(int argc, char *argv[]) {
+  PDFDoc *doc;
+  GooString *fileName = NULL;
+  char *ppmRoot = NULL;
+  char *ppmFile;
+  GooString *ownerPW, *userPW;
+  SplashColor paperColor;
+  SplashOutputDev *splashOut;
+  GBool ok;
+  int exitCode;
+  int pg, pg_num_len;
+  double pg_w, pg_h, tmp;
+
+  exitCode = 99;
+
+  // parse args
+  ok = parseArgs(argDesc, &argc, argv);
+  if (mono && gray) {
+    ok = gFalse;
+  }
+  if ( resolution != 0.0 &&
+       (x_resolution == 150.0 ||
+        y_resolution == 150.0)) {
+    x_resolution = resolution;
+    y_resolution = resolution;
+  }
+  if (!ok || argc > 3 || printVersion || printHelp) {
+    fprintf(stderr, "pdftoppm version %s\n", PACKAGE_VERSION);
+    fprintf(stderr, "%s\n", popplerCopyright);
+    fprintf(stderr, "%s\n", xpdfCopyright);
+    if (!printVersion) {
+      printUsage("pdftoppm", "[PDF-file [PPM-file-prefix]]", argDesc);
+    }
+    if (printVersion || printHelp)
+      exitCode = 0;
+    goto err0;
+  }
+  if (argc > 1) fileName = new GooString(argv[1]);
+  if (argc == 3) ppmRoot = argv[2];
+
+  // read config file
+  globalParams = new GlobalParams();
+  if (enableFreeTypeStr[0]) {
+    if (!globalParams->setEnableFreeType(enableFreeTypeStr)) {
+      fprintf(stderr, "Bad '-freetype' value on command line\n");
+    }
+  }
+  if (antialiasStr[0]) {
+    if (!globalParams->setAntialias(antialiasStr)) {
+      fprintf(stderr, "Bad '-aa' value on command line\n");
+    }
+  }
+  if (vectorAntialiasStr[0]) {
+    if (!globalParams->setVectorAntialias(vectorAntialiasStr)) {
+      fprintf(stderr, "Bad '-aaVector' value on command line\n");
+    }
+  }
+  if (quiet) {
+    globalParams->setErrQuiet(quiet);
+  }
+
+  // open PDF file
+  if (ownerPassword[0]) {
+    ownerPW = new GooString(ownerPassword);
+  } else {
+    ownerPW = NULL;
+  }
+  if (userPassword[0]) {
+    userPW = new GooString(userPassword);
+  } else {
+    userPW = NULL;
+  }
+
+  if (fileName == NULL) {
+    fileName = new GooString("fd://0");
+  }
+  if (fileName->cmp("-") == 0) {
+    delete fileName;
+    fileName = new GooString("fd://0");
+  }
+  doc = PDFDocFactory().createPDFDoc(*fileName, ownerPW, userPW);
+  delete fileName;
+
+  if (userPW) {
+    delete userPW;
+  }
+  if (ownerPW) {
+    delete ownerPW;
+  }
+  if (!doc->isOk()) {
+    exitCode = 1;
+    goto err1;
+  }
+
+  // get page range
+  if (firstPage < 1)
+    firstPage = 1;
+  if (singleFile && lastPage < 1)
+    lastPage = firstPage;
+  if (lastPage < 1 || lastPage > doc->getNumPages())
+    lastPage = doc->getNumPages();
+
+  if (singleFile && firstPage < lastPage) {
+    if (!quiet) {
+      fprintf(stderr,
+        "Warning: Single file will write only the first of the %d pages.\n",
+        lastPage + 1 - firstPage);
+    }
+    lastPage = firstPage;
+  }
+
+  // write PPM files
+#if SPLASH_CMYK
+  if (jpegcmyk || overprint) {
+    globalParams->setOverprintPreview(gTrue);
+    for (int cp = 0; cp < SPOT_NCOMPS+4; cp++)
+      paperColor[cp] = 0;
+  } else 
+#endif
+  {
+    paperColor[0] = 255;
+    paperColor[1] = 255;
+    paperColor[2] = 255;
+  }
+  splashOut = new SplashOutputDev(mono ? splashModeMono1 :
+				    gray ? splashModeMono8 :
+#if SPLASH_CMYK
+				    (jpegcmyk || overprint) ? splashModeDeviceN8 :
+#endif
+				             splashModeRGB8, 4,
+				  gFalse, paperColor);
+  splashOut->startDoc(doc);
+  if (sz != 0) w = h = sz;
+  pg_num_len = numberOfCharacters(doc->getNumPages());
+  for (pg = firstPage; pg <= lastPage; ++pg) {
+    if (printOnlyEven && pg % 2 == 0) continue;
+    if (printOnlyOdd && pg % 2 == 1) continue;
+    if (useCropBox) {
+      pg_w = doc->getPageCropWidth(pg);
+      pg_h = doc->getPageCropHeight(pg);
+    } else {
+      pg_w = doc->getPageMediaWidth(pg);
+      pg_h = doc->getPageMediaHeight(pg);
+    }
+
+    if (scaleTo != 0) {
+      resolution = (72.0 * scaleTo) / (pg_w > pg_h ? pg_w : pg_h);
+      x_resolution = y_resolution = resolution;
+    } else {
+      if (x_scaleTo > 0) {
+        x_resolution = (72.0 * x_scaleTo) / pg_w;
+        if (y_scaleTo == -1)
+          y_resolution = x_resolution;
+      }
+      if (y_scaleTo > 0) {
+        y_resolution = (72.0 * y_scaleTo) / pg_h;
+        if (x_scaleTo == -1)
+          x_resolution = y_resolution;
+      }
+    }
+    pg_w = pg_w * (x_resolution / 72.0);
+    pg_h = pg_h * (y_resolution / 72.0);
+    if ((doc->getPageRotate(pg) == 90) || (doc->getPageRotate(pg) == 270)) {
+      tmp = pg_w;
+      pg_w = pg_h;
+      pg_h = tmp;
+    }
+    if (ppmRoot != NULL) {
+      const char *ext = png ? "png" : (jpeg || jpegcmyk) ? "jpg" : tiff ? "tif" : mono ? "pbm" : gray ? "pgm" : "ppm";
+      if (singleFile) {
+        ppmFile = new char[strlen(ppmRoot) + 1 + strlen(ext) + 1];
+        sprintf(ppmFile, "%s.%s", ppmRoot, ext);
+      } else {
+        ppmFile = new char[strlen(ppmRoot) + 1 + pg_num_len + 1 + strlen(ext) + 1];
+        sprintf(ppmFile, "%s-%0*d.%s", ppmRoot, pg_num_len, pg, ext);
+      }
+      savePageSlice(doc, splashOut, pg, x, y, w, h, pg_w, pg_h, ppmFile);
+      delete[] ppmFile;
+    } else {
+      savePageSlice(doc, splashOut, pg, x, y, w, h, pg_w, pg_h, NULL);
+    }
+  }
+  delete splashOut;
+
+  exitCode = 0;
+
+  // clean up
+ err1:
+  delete doc;
+  delete globalParams;
+ err0:
+
+  // check for memory leaks
+  Object::memCheck(stderr);
+  gMemReport(stderr);
+
+  return exitCode;
+}


More information about the poppler mailing list