[poppler] 5 commits - poppler/OutputDev.h poppler/PSOutputDev.cc poppler/PSOutputDev.h poppler/Stream.cc poppler/Stream.h utils/CMakeLists.txt utils/pdftops.1 utils/pdftops.cc

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Mon Nov 2 17:32:21 UTC 2020


 poppler/OutputDev.h    |    1 
 poppler/PSOutputDev.cc |  146 ++++++++++++++++++++++++++++++++++++++++---------
 poppler/PSOutputDev.h  |   29 ++++++++-
 poppler/Stream.cc      |   82 +++++++++++++++++++++++++++
 poppler/Stream.h       |   52 +++++++++++++++++
 utils/CMakeLists.txt   |    3 +
 utils/pdftops.1        |   13 ++++
 utils/pdftops.cc       |  101 +++++++++++++++++++++++++++++++++
 8 files changed, 395 insertions(+), 32 deletions(-)

New commits:
commit 3c16e88f7c60ec4f49f856ba22d7f20cf626c7d4
Author: Philipp Knechtges <philipp-dev at knechtges.com>
Date:   Mon Sep 28 15:39:12 2020 +0200

    pdftops: fix wording in processcolorformat-related error messages

diff --git a/utils/pdftops.cc b/utils/pdftops.cc
index 560a7b60..072c4d5c 100644
--- a/utils/pdftops.cc
+++ b/utils/pdftops.cc
@@ -311,7 +311,7 @@ int main(int argc, char *argv[])
                 processcolorformat = splashModeCMYK8;
                 processcolorformatspecified = true;
             } else if (processcolorformat != splashModeCMYK8) {
-                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a CMYK profile, but process color format is CMYK.\n", processcolorprofilename.c_str());
+                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is a CMYK profile, but process color format is not CMYK8.\n", processcolorprofilename.c_str());
                 goto err05;
             }
         } else if (displayprofilecolorspace == cmsSigGrayData) {
@@ -319,7 +319,7 @@ int main(int argc, char *argv[])
                 processcolorformat = splashModeMono8;
                 processcolorformatspecified = true;
             } else if (processcolorformat != splashModeMono8) {
-                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a monochrome profile, but process color format is monochrome.\n", processcolorprofilename.c_str());
+                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is a monochrome profile, but process color format is not monochrome.\n", processcolorprofilename.c_str());
                 goto err05;
             }
         } else if (displayprofilecolorspace == cmsSigRgbData) {
@@ -327,7 +327,7 @@ int main(int argc, char *argv[])
                 processcolorformat = splashModeRGB8;
                 processcolorformatspecified = true;
             } else if (processcolorformat != splashModeRGB8) {
-                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a RGB profile, but process color format is RGB.\n", processcolorprofilename.c_str());
+                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is a RGB profile, but process color format is not RGB.\n", processcolorprofilename.c_str());
                 goto err05;
             }
         }
commit e4e261a013719a48bb26dbfc9c95dd177eea0557
Author: Philipp Knechtges <philipp-dev at knechtges.com>
Date:   Mon Sep 28 15:33:09 2020 +0200

    redact splashModeUndefined and as a substitute introduce a boolean value where necessary

diff --git a/poppler/PSOutputDev.cc b/poppler/PSOutputDev.cc
index e77d0ed6..6b99ef2c 100644
--- a/poppler/PSOutputDev.cc
+++ b/poppler/PSOutputDev.cc
@@ -1260,7 +1260,7 @@ void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, PSFileType
     clipURX0 = clipURY0 = -1;
 
 #ifdef HAVE_SPLASH
-    processColorFormat = splashModeUndefined;
+    processColorFormatSpecified = false;
 #endif
 
     // initialize sequential page number
@@ -1385,7 +1385,7 @@ void PSOutputDev::postInit()
 
 #ifdef HAVE_SPLASH
     // set some default process color format if none is set
-    if (processColorFormat == splashModeUndefined) {
+    if (!processColorFormatSpecified) {
         if (level == psLevel1) {
             processColorFormat = splashModeMono8;
         } else if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || globalParams->getOverprintPreview()) {
diff --git a/poppler/PSOutputDev.h b/poppler/PSOutputDev.h
index 02555de5..b93b9dde 100644
--- a/poppler/PSOutputDev.h
+++ b/poppler/PSOutputDev.h
@@ -300,6 +300,7 @@ public:
     {
 #ifdef HAVE_SPLASH
         processColorFormat = splashModeMono8;
+        processColorFormatSpecified = true;
 #endif
     }
 
@@ -338,7 +339,11 @@ public:
     void setEnableFlate(bool b) { enableFlate = b; }
 
 #ifdef HAVE_SPLASH
-    void setProcessColorFormat(SplashColorMode format) { processColorFormat = format; }
+    void setProcessColorFormat(SplashColorMode format)
+    {
+        processColorFormat = format;
+        processColorFormatSpecified = true;
+    }
 #endif
 
 private:
@@ -510,6 +515,7 @@ private:
 
 #ifdef HAVE_SPLASH
     SplashColorMode processColorFormat;
+    bool processColorFormatSpecified;
 #endif
 
     std::unordered_set<std::string> iccEmitted; // contains ICCBased CSAs that have been emitted
diff --git a/splash/SplashTypes.h b/splash/SplashTypes.h
index e780b5e0..0219adaf 100644
--- a/splash/SplashTypes.h
+++ b/splash/SplashTypes.h
@@ -71,8 +71,6 @@ enum SplashColorMode
                        // CMYKSSSSCMYKSSSS...
 };
 
-#define splashModeUndefined ((SplashColorMode)-1)
-
 enum SplashThinLineMode
 {
     splashThinLineDefault, // if SA on: draw solid if requested line width, transformed into
diff --git a/utils/pdftops.cc b/utils/pdftops.cc
index fd4557dc..560a7b60 100644
--- a/utils/pdftops.cc
+++ b/utils/pdftops.cc
@@ -123,7 +123,8 @@ static bool printHelp = false;
 static bool overprint = false;
 #ifdef HAVE_SPLASH
 static GooString processcolorformatname;
-static SplashColorMode processcolorformat = splashModeUndefined;
+static SplashColorMode processcolorformat;
+static bool processcolorformatspecified = false;
 #    ifdef USE_CMS
 static GooString processcolorprofilename;
 static GfxLCMSProfilePtr processcolorprofile;
@@ -279,10 +280,13 @@ int main(int argc, char *argv[])
     if (!processcolorformatname.toStr().empty()) {
         if (processcolorformatname.toStr() == "MONO8") {
             processcolorformat = splashModeMono8;
+            processcolorformatspecified = true;
         } else if (processcolorformatname.toStr() == "CMYK8") {
             processcolorformat = splashModeCMYK8;
+            processcolorformatspecified = true;
         } else if (processcolorformatname.toStr() == "RGB8") {
             processcolorformat = splashModeRGB8;
+            processcolorformatspecified = true;
         } else {
             fprintf(stderr, "Error: Unknown process color format \"%s\".\n", processcolorformatname.c_str());
             goto err05;
@@ -303,22 +307,25 @@ int main(int argc, char *argv[])
         }
         displayprofilecolorspace = cmsGetColorSpace(processcolorprofile.get());
         if (displayprofilecolorspace == cmsSigCmykData) {
-            if (processcolorformat == splashModeUndefined) {
+            if (!processcolorformatspecified) {
                 processcolorformat = splashModeCMYK8;
+                processcolorformatspecified = true;
             } else if (processcolorformat != splashModeCMYK8) {
                 fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a CMYK profile, but process color format is CMYK.\n", processcolorprofilename.c_str());
                 goto err05;
             }
         } else if (displayprofilecolorspace == cmsSigGrayData) {
-            if (processcolorformat == splashModeUndefined) {
+            if (!processcolorformatspecified) {
                 processcolorformat = splashModeMono8;
+                processcolorformatspecified = true;
             } else if (processcolorformat != splashModeMono8) {
                 fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a monochrome profile, but process color format is monochrome.\n", processcolorprofilename.c_str());
                 goto err05;
             }
         } else if (displayprofilecolorspace == cmsSigRgbData) {
-            if (processcolorformat == splashModeUndefined) {
+            if (!processcolorformatspecified) {
                 processcolorformat = splashModeRGB8;
+                processcolorformatspecified = true;
             } else if (processcolorformat != splashModeRGB8) {
                 fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a RGB profile, but process color format is RGB.\n", processcolorprofilename.c_str());
                 goto err05;
@@ -327,7 +334,7 @@ int main(int argc, char *argv[])
     }
 #    endif
 
-    if (processcolorformat != splashModeUndefined) {
+    if (processcolorformatspecified) {
         if (level1 && processcolorformat != splashModeMono8) {
             fprintf(stderr, "Error: Setting -level1 requires -processcolorformat MONO8");
             goto err05;
@@ -444,7 +451,8 @@ int main(int argc, char *argv[])
         psOut->setRasterResolution(splashResolution);
     }
 #ifdef HAVE_SPLASH
-    psOut->setProcessColorFormat(processcolorformat);
+    if (processcolorformatspecified)
+        psOut->setProcessColorFormat(processcolorformat);
 #    ifdef USE_CMS
     psOut->setDisplayProfile(processcolorprofile);
 #    endif
commit 75abc683868d8933f10128074a4234afaf76e77f
Author: Philipp Knechtges <philipp-dev at knechtges.com>
Date:   Sun Sep 13 19:59:41 2020 +0200

    PSOutputDev: use the DeviceN8 bitmap for rasterization with CMYK-output + overprint
    
    This mostly mimics the pdftoppm behaviour.

diff --git a/poppler/PSOutputDev.cc b/poppler/PSOutputDev.cc
index 67ae28fe..e77d0ed6 100644
--- a/poppler/PSOutputDev.cc
+++ b/poppler/PSOutputDev.cc
@@ -3141,6 +3141,7 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
     unsigned char digit;
     bool isOptimizedGray;
     bool overprint;
+    SplashColorMode internalColorFormat;
 #endif
 
     if (!postInitDone) {
@@ -3176,13 +3177,25 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
     startPage(page->getNum(), state, xref);
     delete state;
 
+    // If we would not rasterize this page, we would emit the overprint code anyway for language level 2 and upwards.
+    // As such it is safe to assume for a CMYK printer that it would respect the overprint operands.
+    overprint = globalParams->getOverprintPreview() || (processColorFormat == splashModeCMYK8 && level >= psLevel2);
+
     // set up the SplashOutputDev
+    internalColorFormat = processColorFormat;
     if (processColorFormat == splashModeMono8) {
         numComps = 1;
         paperColor[0] = 0xff;
     } else if (processColorFormat == splashModeCMYK8) {
         numComps = 4;
         paperColor[0] = paperColor[1] = paperColor[2] = paperColor[3] = 0;
+
+        // If overprinting is emulated, it is not sufficient to just store the CMYK values in a bitmap.
+        // All separation channels need to be stored and collapsed at the end.
+        // Cf. PDF32000_2008 Section 11.7.4.5 and Tables 148, 149
+        if (overprint) {
+            internalColorFormat = splashModeDeviceN8;
+        }
     } else if (processColorFormat == splashModeRGB8) {
         numComps = 3;
         paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
@@ -3192,10 +3205,7 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
         numComps = 3;
         paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
     }
-    // If we would not rasterize this page, we would emit the overprint code anyway for language level 2 and upwards.
-    // As such it is safe to assume for a CMYK printer that it would respect the overprint operands.
-    overprint = globalParams->getOverprintPreview() || (processColorFormat == splashModeCMYK8 && level >= psLevel2);
-    splashOut = new SplashOutputDev(processColorFormat, 1, false, paperColor, false, splashThinLineDefault, overprint);
+    splashOut = new SplashOutputDev(internalColorFormat, 1, false, paperColor, false, splashThinLineDefault, overprint);
     splashOut->setFontAntialias(rasterAntialias);
     splashOut->setVectorAntialias(rasterAntialias);
 #    ifdef USE_CMS
@@ -3440,7 +3450,11 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
         case psLevel3:
         case psLevel3Sep:
             p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize();
-            str0 = new MemStream((char *)p, 0, w * h * numComps, Object(objNull));
+            if (processColorFormat == splashModeCMYK8 && internalColorFormat != splashModeCMYK8) {
+                str0 = new SplashBitmapCMYKEncoder(bitmap);
+            } else {
+                str0 = new MemStream((char *)p, 0, w * h * numComps, Object(objNull));
+            }
             // Check for a color image that uses only gray
             if (!getOptimizeColorSpace()) {
                 isOptimizedGray = false;
diff --git a/poppler/Stream.cc b/poppler/Stream.cc
index 4971e2ed..ab850a91 100644
--- a/poppler/Stream.cc
+++ b/poppler/Stream.cc
@@ -68,6 +68,10 @@
 #include "Stream-CCITT.h"
 #include "CachedFile.h"
 
+#ifdef HAVE_SPLASH
+#    include "splash/SplashBitmap.h"
+#endif
+
 #ifdef ENABLE_LIBJPEG
 #    include "DCTStream.h"
 #endif
@@ -5137,3 +5141,81 @@ bool RGBGrayEncoder::fillBuf()
     *bufEnd++ = (char)i;
     return true;
 }
+
+//------------------------------------------------------------------------
+// SplashBitmapCMYKEncoder
+//------------------------------------------------------------------------
+
+#ifdef HAVE_SPLASH
+SplashBitmapCMYKEncoder::SplashBitmapCMYKEncoder(SplashBitmap *bitmapA) : bitmap(bitmapA)
+{
+    width = (size_t)4 * bitmap->getWidth();
+    height = bitmap->getHeight();
+    buf.resize(width);
+    bufPtr = width;
+    curLine = height - 1;
+}
+
+SplashBitmapCMYKEncoder::~SplashBitmapCMYKEncoder() { }
+
+void SplashBitmapCMYKEncoder::reset()
+{
+    bufPtr = width;
+    curLine = height - 1;
+}
+
+int SplashBitmapCMYKEncoder::lookChar()
+{
+    if (bufPtr >= width && !fillBuf()) {
+        return EOF;
+    }
+    return buf[bufPtr];
+}
+
+int SplashBitmapCMYKEncoder::getChar()
+{
+    int ret = lookChar();
+    bufPtr++;
+    return ret;
+}
+
+bool SplashBitmapCMYKEncoder::fillBuf()
+{
+    if (curLine < 0) {
+        return false;
+    }
+
+    if (bufPtr < width) {
+        return true;
+    }
+
+    bitmap->getCMYKLine(curLine, &buf[0]);
+    bufPtr = 0;
+    curLine--;
+    return true;
+}
+
+Goffset SplashBitmapCMYKEncoder::getPos()
+{
+    return (height - 1 - curLine) * width + bufPtr;
+}
+
+void SplashBitmapCMYKEncoder::setPos(Goffset pos, int dir)
+{
+    // This code is mostly untested!
+    if (dir < 0) {
+        curLine = pos / width;
+    } else {
+        curLine = height - 1 - pos / width;
+    }
+
+    bufPtr = width;
+    fillBuf();
+
+    if (dir < 0) {
+        bufPtr = width - 1 - pos % width;
+    } else {
+        bufPtr = pos % width;
+    }
+}
+#endif
diff --git a/poppler/Stream.h b/poppler/Stream.h
index c6b363f6..720811ba 100644
--- a/poppler/Stream.h
+++ b/poppler/Stream.h
@@ -47,6 +47,9 @@
 class GooFile;
 class BaseStream;
 class CachedFile;
+#ifdef HAVE_SPLASH
+class SplashBitmap;
+#endif
 
 //------------------------------------------------------------------------
 
@@ -1428,4 +1431,53 @@ private:
     bool fillBuf();
 };
 
+//------------------------------------------------------------------------
+// SplashBitmapCMYKEncoder
+//
+// This stream helps to condense SplashBitmaps (mostly of DeviceN8 type) into
+// pure CMYK colors. In particular for a DeviceN8 bitmap it redacts the spot colorants.
+//------------------------------------------------------------------------
+
+#ifdef HAVE_SPLASH
+class SplashBitmapCMYKEncoder : public Stream
+{
+public:
+    SplashBitmapCMYKEncoder(SplashBitmap *bitmapA);
+    ~SplashBitmapCMYKEncoder() override;
+    StreamKind getKind() const override { return strWeird; }
+    void reset() override;
+    int getChar() override;
+    int lookChar() override;
+    GooString *getPSFilter(int /*psLevel*/, const char * /*indent*/) override { return nullptr; }
+    bool isBinary(bool /*last = true*/) override { return true; }
+
+    // Although we are an encoder, we return false here, since we do not want do be auto-deleted by
+    // successive streams.
+    bool isEncoder() override { return false; }
+
+    int getUnfilteredChar() override { return getChar(); }
+    void unfilteredReset() override { reset(); }
+
+    BaseStream *getBaseStream() override { return nullptr; }
+    Stream *getUndecodedStream() override { return this; }
+
+    Dict *getDict() override { return nullptr; }
+    Object *getDictObject() override { return nullptr; }
+
+    Goffset getPos() override;
+    void setPos(Goffset pos, int dir = 0) override;
+
+private:
+    SplashBitmap *bitmap;
+    size_t width;
+    int height;
+
+    std::vector<unsigned char> buf;
+    size_t bufPtr;
+    int curLine;
+
+    bool fillBuf();
+};
+#endif
+
 #endif
commit 0c772071baa74b11c66981f768dccf123f431ce1
Author: Philipp Knechtges <philipp-dev at knechtges.com>
Date:   Sun Jun 7 21:42:57 2020 +0200

    PSOutputDev/pdftops: for splashModeCMYK8 and language level >=2 activate overprint emulation
    
    The overprint operands are always emitted for any language level >= 2, such that it is just consistent
    to activate overprint emulation during rasterization if it is quite likely that the final device
    supports this operation.

diff --git a/poppler/PSOutputDev.cc b/poppler/PSOutputDev.cc
index ff2a21cb..67ae28fe 100644
--- a/poppler/PSOutputDev.cc
+++ b/poppler/PSOutputDev.cc
@@ -3140,6 +3140,7 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
     char hexBuf[32 * 2 + 2]; // 32 values X 2 chars/value + line ending + null
     unsigned char digit;
     bool isOptimizedGray;
+    bool overprint;
 #endif
 
     if (!postInitDone) {
@@ -3191,7 +3192,10 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
         numComps = 3;
         paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
     }
-    splashOut = new SplashOutputDev(processColorFormat, 1, false, paperColor, false);
+    // If we would not rasterize this page, we would emit the overprint code anyway for language level 2 and upwards.
+    // As such it is safe to assume for a CMYK printer that it would respect the overprint operands.
+    overprint = globalParams->getOverprintPreview() || (processColorFormat == splashModeCMYK8 && level >= psLevel2);
+    splashOut = new SplashOutputDev(processColorFormat, 1, false, paperColor, false, splashThinLineDefault, overprint);
     splashOut->setFontAntialias(rasterAntialias);
     splashOut->setVectorAntialias(rasterAntialias);
 #    ifdef USE_CMS
diff --git a/utils/pdftops.1 b/utils/pdftops.1
index 7ec85cf8..00e19856 100644
--- a/utils/pdftops.1
+++ b/utils/pdftops.1
@@ -205,7 +205,9 @@ bypass all security restrictions.
 Specify the user password for the PDF file.
 .TP
 .B \-overprint
-Enable overprinting.
+Enable overprint emulation during rasterization. For \-processcolorformat being CMYK8 and the language level
+being higher than 2, this option is set to true by default. Note: This option requires \-processcolorformat to
+be CMYK8.
 .TP
 .B \-q
 Don't print any messages or errors.
diff --git a/utils/pdftops.cc b/utils/pdftops.cc
index 6708e0bd..fd4557dc 100644
--- a/utils/pdftops.cc
+++ b/utils/pdftops.cc
@@ -172,7 +172,7 @@ static const ArgDesc argDesc[] = { { "-f", argInt, &firstPage, 0, "first page to
                                    { "-duplex", argFlag, &duplex, 0, "enable duplex printing" },
                                    { "-opw", argString, ownerPassword, sizeof(ownerPassword), "owner password (for encrypted files)" },
                                    { "-upw", argString, userPassword, sizeof(userPassword), "user password (for encrypted files)" },
-                                   { "-overprint", argFlag, &overprint, 0, "enable overprint" },
+                                   { "-overprint", argFlag, &overprint, 0, "enable overprint emulation during rasterization" },
                                    { "-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" },
commit ccdee13835b66892355ebe1bb8d27556edee32be
Author: Philipp Knechtges <philipp-dev at knechtges.com>
Date:   Sun Jun 7 18:34:25 2020 +0200

    PSOutputDev/pdftops: provide options to set the rasterization color space and ICC profile
    
    Poppler so far has to assume some underlying color space. This commit gives the user the
    opportunity to specify this color space through two new options in pdftops:
      -processcolorformat
      -processcolorprofile
    
    The former practically sets the underlying splash bitmap type during rasterization,
    while the latter sets the display profile of the used SplashOutputDev.

diff --git a/poppler/OutputDev.h b/poppler/OutputDev.h
index 22d00a2b..d2a8e3c8 100644
--- a/poppler/OutputDev.h
+++ b/poppler/OutputDev.h
@@ -323,6 +323,7 @@ public:
 
 #ifdef USE_CMS
     void setDisplayProfile(const GfxLCMSProfilePtr &profile) { displayprofile = profile; }
+    GfxLCMSProfilePtr getDisplayProfile() const { return displayprofile; }
 
     PopplerCache<Ref, GfxICCBasedColorSpace> *getIccColorSpaceCache() { return &iccColorSpaceCache; }
 #endif
diff --git a/poppler/PSOutputDev.cc b/poppler/PSOutputDev.cc
index f836947c..ff2a21cb 100644
--- a/poppler/PSOutputDev.cc
+++ b/poppler/PSOutputDev.cc
@@ -87,6 +87,10 @@
 #include "PSOutputDev.h"
 #include "PDFDoc.h"
 
+#ifdef USE_CMS
+#    include <lcms2.h>
+#endif
+
 // the MSVC math.h doesn't define this
 #ifndef M_PI
 #    define M_PI 3.14159265358979323846
@@ -1212,7 +1216,6 @@ void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, PSFileType
     useBinary = false;
     enableLZW = true;
     enableFlate = true;
-    rasterMono = false;
     rasterResolution = 300;
     uncompressPreloadedImages = false;
     psCenter = true;
@@ -1256,6 +1259,10 @@ void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, PSFileType
     clipLLX0 = clipLLY0 = 0;
     clipURX0 = clipURY0 = -1;
 
+#ifdef HAVE_SPLASH
+    processColorFormat = splashModeUndefined;
+#endif
+
     // initialize sequential page number
     seqPage = 1;
 }
@@ -1376,6 +1383,63 @@ void PSOutputDev::postInit()
     numTilingPatterns = 0;
     nextFunc = 0;
 
+#ifdef HAVE_SPLASH
+    // set some default process color format if none is set
+    if (processColorFormat == splashModeUndefined) {
+        if (level == psLevel1) {
+            processColorFormat = splashModeMono8;
+        } else if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || globalParams->getOverprintPreview()) {
+            processColorFormat = splashModeCMYK8;
+        }
+#    ifdef USE_CMS
+        else if (getDisplayProfile()) {
+            auto processcolorspace = cmsGetColorSpace(getDisplayProfile().get());
+            if (processcolorspace == cmsSigCmykData) {
+                processColorFormat = splashModeCMYK8;
+            } else if (processcolorspace == cmsSigGrayData) {
+                processColorFormat = splashModeMono8;
+            } else {
+                processColorFormat = splashModeRGB8;
+            }
+        }
+#    endif
+        else {
+            processColorFormat = splashModeRGB8;
+        }
+    }
+
+    // check for consistency between the processColorFormat the LanguageLevel and other settings
+    if (level == psLevel1 && processColorFormat != splashModeMono8) {
+        error(errConfig, -1,
+              "Conflicting settings between LanguageLevel=psLevel1 and processColorFormat."
+              " Resetting processColorFormat to MONO8.");
+        processColorFormat = splashModeMono8;
+    } else if ((level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || globalParams->getOverprintPreview()) && processColorFormat != splashModeCMYK8) {
+        error(errConfig, -1,
+              "Conflicting settings between LanguageLevel and/or overprint simulation, and processColorFormat."
+              " Resetting processColorFormat to CMYK8.");
+        processColorFormat = splashModeCMYK8;
+    }
+#    ifdef USE_CMS
+    if (getDisplayProfile()) {
+        auto processcolorspace = cmsGetColorSpace(getDisplayProfile().get());
+        if (processColorFormat == splashModeCMYK8) {
+            if (processcolorspace != cmsSigCmykData) {
+                error(errConfig, -1, "Mismatch between processColorFormat=CMYK8 and ICC profile color format.");
+            }
+        } else if (processColorFormat == splashModeMono8) {
+            if (processcolorspace != cmsSigGrayData) {
+                error(errConfig, -1, "Mismatch between processColorFormat=MONO8 and ICC profile color format.");
+            }
+        } else if (processColorFormat == splashModeRGB8) {
+            if (processcolorspace != cmsSigRgbData) {
+                error(errConfig, -1, "Mismatch between processColorFormat=RGB8 and ICC profile color format.");
+            }
+        }
+    }
+#    endif
+#endif
+
     // initialize embedded font resource comment list
     embFontList = new GooString();
 
@@ -3075,7 +3139,7 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
     int numComps, initialNumComps;
     char hexBuf[32 * 2 + 2]; // 32 values X 2 chars/value + line ending + null
     unsigned char digit;
-    bool isGray;
+    bool isOptimizedGray;
 #endif
 
     if (!postInitDone) {
@@ -3112,21 +3176,27 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
     delete state;
 
     // set up the SplashOutputDev
-    if (rasterMono || level == psLevel1) {
+    if (processColorFormat == splashModeMono8) {
         numComps = 1;
         paperColor[0] = 0xff;
-        splashOut = new SplashOutputDev(splashModeMono8, 1, false, paperColor, false);
-    } else if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep || globalParams->getOverprintPreview()) {
+    } else if (processColorFormat == splashModeCMYK8) {
         numComps = 4;
         paperColor[0] = paperColor[1] = paperColor[2] = paperColor[3] = 0;
-        splashOut = new SplashOutputDev(splashModeCMYK8, 1, false, paperColor, false);
+    } else if (processColorFormat == splashModeRGB8) {
+        numComps = 3;
+        paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
     } else {
+        error(errUnimplemented, -1, "Unsupported processColorMode. Falling back to RGB8.");
+        processColorFormat = splashModeRGB8;
         numComps = 3;
         paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
-        splashOut = new SplashOutputDev(splashModeRGB8, 1, false, paperColor, false);
     }
+    splashOut = new SplashOutputDev(processColorFormat, 1, false, paperColor, false);
     splashOut->setFontAntialias(rasterAntialias);
     splashOut->setVectorAntialias(rasterAntialias);
+#    ifdef USE_CMS
+    splashOut->setDisplayProfile(getDisplayProfile());
+#    endif
     splashOut->startDoc(doc);
 
     // break the page into stripes
@@ -3211,11 +3281,11 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
             p = bitmap->getDataPtr();
             // Check for an all gray image
             if (getOptimizeColorSpace()) {
-                isGray = true;
+                isOptimizedGray = true;
                 for (y = 0; y < h; ++y) {
                     for (x = 0; x < w; ++x) {
                         if (p[4 * x] != p[4 * x + 1] || p[4 * x] != p[4 * x + 2]) {
-                            isGray = false;
+                            isOptimizedGray = false;
                             y = h;
                             break;
                         }
@@ -3223,13 +3293,13 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
                     p += bitmap->getRowSize();
                 }
             } else {
-                isGray = false;
+                isOptimizedGray = false;
             }
-            writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1{5:s}{6:s}\n", w, h, w, -h, h, isGray ? "" : "Sep", useBinary ? "Bin" : "");
+            writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1{5:s}{6:s}\n", w, h, w, -h, h, isOptimizedGray ? "" : "Sep", useBinary ? "Bin" : "");
             p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize();
             i = 0;
             col[0] = col[1] = col[2] = col[3] = 0;
-            if (isGray) {
+            if (isOptimizedGray) {
                 int g;
                 if ((psProcessBlack & processColors) == 0) {
                     // Check if the image uses black
@@ -3369,36 +3439,36 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
             str0 = new MemStream((char *)p, 0, w * h * numComps, Object(objNull));
             // Check for a color image that uses only gray
             if (!getOptimizeColorSpace()) {
-                isGray = false;
+                isOptimizedGray = false;
             } else if (numComps == 4) {
                 int compCyan;
-                isGray = true;
+                isOptimizedGray = true;
                 while ((compCyan = str0->getChar()) != EOF) {
                     if (str0->getChar() != compCyan || str0->getChar() != compCyan) {
-                        isGray = false;
+                        isOptimizedGray = false;
                         break;
                     }
                     str0->getChar();
                 }
             } else if (numComps == 3) {
                 int compRed;
-                isGray = true;
+                isOptimizedGray = true;
                 while ((compRed = str0->getChar()) != EOF) {
                     if (str0->getChar() != compRed || str0->getChar() != compRed) {
-                        isGray = false;
+                        isOptimizedGray = false;
                         break;
                     }
                 }
             } else {
-                isGray = false;
+                isOptimizedGray = false;
             }
             str0->reset();
 #    ifdef ENABLE_ZLIB
             if (useFlate) {
-                if (isGray && numComps == 4) {
+                if (isOptimizedGray && numComps == 4) {
                     str = new FlateEncoder(new CMYKGrayEncoder(str0));
                     numComps = 1;
-                } else if (isGray && numComps == 3) {
+                } else if (isOptimizedGray && numComps == 3) {
                     str = new FlateEncoder(new RGBGrayEncoder(str0));
                     numComps = 1;
                 } else {
@@ -3407,20 +3477,20 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
             } else
 #    endif
                     if (useLZW) {
-                if (isGray && numComps == 4) {
+                if (isOptimizedGray && numComps == 4) {
                     str = new LZWEncoder(new CMYKGrayEncoder(str0));
                     numComps = 1;
-                } else if (isGray && numComps == 3) {
+                } else if (isOptimizedGray && numComps == 3) {
                     str = new LZWEncoder(new RGBGrayEncoder(str0));
                     numComps = 1;
                 } else {
                     str = new LZWEncoder(str0);
                 }
             } else {
-                if (isGray && numComps == 4) {
+                if (isOptimizedGray && numComps == 4) {
                     str = new RunLengthEncoder(new CMYKGrayEncoder(str0));
                     numComps = 1;
-                } else if (isGray && numComps == 3) {
+                } else if (isOptimizedGray && numComps == 3) {
                     str = new RunLengthEncoder(new RGBGrayEncoder(str0));
                     numComps = 1;
                 } else {
@@ -3440,7 +3510,13 @@ bool PSOutputDev::checkPageSlice(Page *page, double /*hDPI*/, double /*vDPI*/, i
             writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", w, -h, h);
             writePS("  /BitsPerComponent 8\n");
             if (numComps == 1) {
-                writePS("  /Decode [1 0]\n");
+                // the optimized gray variants are implemented as a subtractive color space,
+                // such that the range is flipped for them
+                if (isOptimizedGray) {
+                    writePS("  /Decode [1 0]\n");
+                } else {
+                    writePS("  /Decode [0 1]\n");
+                }
             } else if (numComps == 3) {
                 writePS("  /Decode [0 1 0 1 0 1]\n");
             } else {
diff --git a/poppler/PSOutputDev.h b/poppler/PSOutputDev.h
index 3a51c0bd..02555de5 100644
--- a/poppler/PSOutputDev.h
+++ b/poppler/PSOutputDev.h
@@ -50,6 +50,10 @@
 #include <unordered_map>
 #include <string>
 
+#ifdef HAVE_SPLASH
+#    include "splash/Splash.h"
+#endif
+
 class PDFDoc;
 class XRef;
 class Function;
@@ -292,7 +296,13 @@ public:
     void setRasterAntialias(bool a) { rasterAntialias = a; }
     void setForceRasterize(PSForceRasterize f) { forceRasterize = f; }
     void setRasterResolution(double r) { rasterResolution = r; }
-    void setRasterMono(bool b) { rasterMono = b; }
+    void setRasterMono(bool b)
+    {
+#ifdef HAVE_SPLASH
+        processColorFormat = splashModeMono8;
+#endif
+    }
+
     void setUncompressPreloadedImages(bool b) { uncompressPreloadedImages = b; }
 
     bool getEmbedType1() const { return embedType1; }
@@ -327,6 +337,10 @@ public:
     void setEnableLZW(bool b) { enableLZW = b; }
     void setEnableFlate(bool b) { enableFlate = b; }
 
+#ifdef HAVE_SPLASH
+    void setProcessColorFormat(SplashColorMode format) { processColorFormat = format; }
+#endif
+
 private:
     void init(PSOutputFunc outputFuncA, void *outputStreamA, PSFileType fileTypeA, char *psTitleA, PDFDoc *doc, const std::vector<int> &pages, PSOutMode modeA, int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, bool manualCtrlA,
               int paperWidthA, int paperHeightA, bool noCropA, bool duplexA);
@@ -476,9 +490,6 @@ private:
     bool rasterAntialias; // antialias on rasterize
     bool uncompressPreloadedImages;
     double rasterResolution; // PostScript rasterization resolution (dpi)
-    bool rasterMono; // true to do PostScript rasterization
-                     //   in monochrome (gray); false to do it
-                     //   in color (RGB/CMYK)
     bool embedType1; // embed Type 1 fonts?
     bool embedTrueType; // embed TrueType fonts?
     bool embedCIDPostScript; // embed CID PostScript fonts?
@@ -497,6 +508,10 @@ private:
     bool enableLZW; // enable LZW compression
     bool enableFlate; // enable Flate compression
 
+#ifdef HAVE_SPLASH
+    SplashColorMode processColorFormat;
+#endif
+
     std::unordered_set<std::string> iccEmitted; // contains ICCBased CSAs that have been emitted
 
 #ifdef OPI_SUPPORT
diff --git a/splash/SplashTypes.h b/splash/SplashTypes.h
index 0219adaf..e780b5e0 100644
--- a/splash/SplashTypes.h
+++ b/splash/SplashTypes.h
@@ -71,6 +71,8 @@ enum SplashColorMode
                        // CMYKSSSSCMYKSSSS...
 };
 
+#define splashModeUndefined ((SplashColorMode)-1)
+
 enum SplashThinLineMode
 {
     splashThinLineDefault, // if SA on: draw solid if requested line width, transformed into
diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt
index b20e744f..a5951795 100644
--- a/utils/CMakeLists.txt
+++ b/utils/CMakeLists.txt
@@ -114,6 +114,9 @@ set(pdftops_SOURCES ${common_srcs}
 )
 add_executable(pdftops ${pdftops_SOURCES})
 target_link_libraries(pdftops ${common_libs})
+if(LCMS2_FOUND)
+  target_link_libraries(pdftops ${LCMS2_LIBRARIES})
+endif()
 install(TARGETS pdftops DESTINATION bin)
 install(FILES pdftops.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
 
diff --git a/utils/pdftops.1 b/utils/pdftops.1
index 28431fbc..7ec85cf8 100644
--- a/utils/pdftops.1
+++ b/utils/pdftops.1
@@ -141,6 +141,15 @@ By default, pdftops rasterizes pages as needed, for example, if they contain tra
 To force rasterization, set \-rasterize to "always". Use this to eliminate fonts.
 To prevent rasterization, set \-rasterize to "never". This may produce files that display incorrectly.
 .TP
+.BI \-processcolorformat " MONO8 | CMYK8 | RGB8"
+Sets the process color format as it is used during rasterization and transparency reduction.
+The default depends on the other settings: For \-level1 the default is MONO8, for \-level{1,2,3}sep
+or \-overprint the default is CMYK8, and in all other cases RGB8 is the default. If \-processcolorprofile
+is given then \-processcolorformat is inferred from the specified ICC profile.
+.TP
+.BI \-processcolorprofile " filename"
+Sets the ICC profile that is assumed during rasterization and transparency reduction.
+.TP
 .B \-optimizecolorspace
 By default, bitmap images in the PDF pass through to the output PostScript
 in their original color space, which produces predictable results.
diff --git a/utils/pdftops.cc b/utils/pdftops.cc
index 7aa794e1..6708e0bd 100644
--- a/utils/pdftops.cc
+++ b/utils/pdftops.cc
@@ -55,6 +55,10 @@
 #include "Error.h"
 #include "Win32Console.h"
 
+#ifdef USE_CMS
+#    include <lcms2.h>
+#endif
+
 static bool setPSPaperSize(char *size, int &psPaperWidth, int &psPaperHeight)
 {
     if (!strcmp(size, "match")) {
@@ -117,6 +121,14 @@ static bool quiet = false;
 static bool printVersion = false;
 static bool printHelp = false;
 static bool overprint = false;
+#ifdef HAVE_SPLASH
+static GooString processcolorformatname;
+static SplashColorMode processcolorformat = splashModeUndefined;
+#    ifdef USE_CMS
+static GooString processcolorprofilename;
+static GfxLCMSProfilePtr processcolorprofile;
+#    endif
+#endif
 
 static const ArgDesc argDesc[] = { { "-f", argInt, &firstPage, 0, "first page to print" },
                                    { "-l", argInt, &lastPage, 0, "last page to print" },
@@ -141,6 +153,12 @@ static const ArgDesc argDesc[] = { { "-f", argInt, &firstPage, 0, "first page to
                                    { "-passfonts", argFlag, &fontPassthrough, 0, "don't substitute missing fonts" },
                                    { "-aaRaster", argString, rasterAntialiasStr, sizeof(rasterAntialiasStr), "enable anti-aliasing on rasterization: yes, no" },
                                    { "-rasterize", argString, forceRasterizeStr, sizeof(forceRasterizeStr), "control rasterization: always, never, whenneeded" },
+#ifdef HAVE_SPLASH
+                                   { "-processcolorformat", argGooString, &processcolorformatname, 0, "color format that is used during rasterization and transparency reduction: MONO8, RGB8, CMYK8" },
+#    ifdef USE_CMS
+                                   { "-processcolorprofile", argGooString, &processcolorprofilename, 0, "ICC color profile to use as the process color profile during rasterization and transparency reduction" },
+#    endif
+#endif
                                    { "-optimizecolorspace", argFlag, &optimizeColorSpace, 0, "convert gray RGB images to gray color space" },
                                    { "-passlevel1customcolor", argFlag, &passLevel1CustomColor, 0, "pass custom color in level1sep" },
                                    { "-preload", argFlag, &preload, 0, "preload images and forms" },
@@ -176,6 +194,9 @@ int main(int argc, char *argv[])
     int exitCode;
     bool rasterAntialias = false;
     std::vector<int> pages;
+#ifdef USE_CMS
+    cmsColorSpaceSignature displayprofilecolorspace;
+#endif
 
     Win32Console win32Console(&argc, &argv);
     exitCode = 99;
@@ -254,6 +275,69 @@ int main(int argc, char *argv[])
         globalParams->setErrQuiet(quiet);
     }
 
+#ifdef HAVE_SPLASH
+    if (!processcolorformatname.toStr().empty()) {
+        if (processcolorformatname.toStr() == "MONO8") {
+            processcolorformat = splashModeMono8;
+        } else if (processcolorformatname.toStr() == "CMYK8") {
+            processcolorformat = splashModeCMYK8;
+        } else if (processcolorformatname.toStr() == "RGB8") {
+            processcolorformat = splashModeRGB8;
+        } else {
+            fprintf(stderr, "Error: Unknown process color format \"%s\".\n", processcolorformatname.c_str());
+            goto err05;
+        }
+    }
+
+#    ifdef USE_CMS
+    if (!processcolorprofilename.toStr().empty()) {
+        processcolorprofile = make_GfxLCMSProfilePtr(cmsOpenProfileFromFile(processcolorprofilename.c_str(), "r"));
+        if (!processcolorprofile) {
+            fprintf(stderr, "Error: Could not open the ICC profile \"%s\".\n", processcolorprofilename.c_str());
+            goto err05;
+        }
+        if (!cmsIsIntentSupported(processcolorprofile.get(), INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_OUTPUT) && !cmsIsIntentSupported(processcolorprofile.get(), INTENT_ABSOLUTE_COLORIMETRIC, LCMS_USED_AS_OUTPUT)
+            && !cmsIsIntentSupported(processcolorprofile.get(), INTENT_SATURATION, LCMS_USED_AS_OUTPUT) && !cmsIsIntentSupported(processcolorprofile.get(), INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT)) {
+            fprintf(stderr, "Error: ICC profile \"%s\" is not an output profile.\n", processcolorprofilename.c_str());
+            goto err05;
+        }
+        displayprofilecolorspace = cmsGetColorSpace(processcolorprofile.get());
+        if (displayprofilecolorspace == cmsSigCmykData) {
+            if (processcolorformat == splashModeUndefined) {
+                processcolorformat = splashModeCMYK8;
+            } else if (processcolorformat != splashModeCMYK8) {
+                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a CMYK profile, but process color format is CMYK.\n", processcolorprofilename.c_str());
+                goto err05;
+            }
+        } else if (displayprofilecolorspace == cmsSigGrayData) {
+            if (processcolorformat == splashModeUndefined) {
+                processcolorformat = splashModeMono8;
+            } else if (processcolorformat != splashModeMono8) {
+                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a monochrome profile, but process color format is monochrome.\n", processcolorprofilename.c_str());
+                goto err05;
+            }
+        } else if (displayprofilecolorspace == cmsSigRgbData) {
+            if (processcolorformat == splashModeUndefined) {
+                processcolorformat = splashModeRGB8;
+            } else if (processcolorformat != splashModeRGB8) {
+                fprintf(stderr, "Error: Supplied ICC profile \"%s\" is not a RGB profile, but process color format is RGB.\n", processcolorprofilename.c_str());
+                goto err05;
+            }
+        }
+    }
+#    endif
+
+    if (processcolorformat != splashModeUndefined) {
+        if (level1 && processcolorformat != splashModeMono8) {
+            fprintf(stderr, "Error: Setting -level1 requires -processcolorformat MONO8");
+            goto err05;
+        } else if ((level1Sep || level2Sep || level3Sep || overprint) && processcolorformat != splashModeCMYK8) {
+            fprintf(stderr, "Error: Setting -level1sep/-level2sep/-level3sep/-overprint requires -processcolorformat CMYK8");
+            goto err05;
+        }
+    }
+#endif
+
     // open PDF file
     if (ownerPassword[0] != '\001') {
         ownerPW = new GooString(ownerPassword);
@@ -359,6 +443,12 @@ int main(int argc, char *argv[])
     if (splashResolution > 0) {
         psOut->setRasterResolution(splashResolution);
     }
+#ifdef HAVE_SPLASH
+    psOut->setProcessColorFormat(processcolorformat);
+#    ifdef USE_CMS
+    psOut->setDisplayProfile(processcolorprofile);
+#    endif
+#endif
     psOut->setEmbedType1(!noEmbedT1Fonts);
     psOut->setEmbedTrueType(!noEmbedTTFonts);
     psOut->setEmbedCIDPostScript(!noEmbedCIDPSFonts);
@@ -391,6 +481,7 @@ err2:
     delete psFileName;
 err1:
     delete doc;
+err05:
     delete fileName;
 err0:
 


More information about the poppler mailing list