1 //========================================================================
2 //
3 // pdftoppm.cc
4 //
5 // Copyright 2003 Glyph & Cog, LLC
6 //
7 //========================================================================
8 
9 //========================================================================
10 //
11 // Modified under the Poppler project - http://poppler.freedesktop.org
12 //
13 // All changes made under the Poppler project to this file are licensed
14 // under GPL version 2 or later
15 //
16 // Copyright (C) 2007 Ilmari Heikkinen <ilmari.heikkinen@gmail.com>
17 // Copyright (C) 2008 Richard Airlie <richard.airlie@maglabs.net>
18 // Copyright (C) 2009 Michael K. Johnson <a1237@danlj.org>
19 // Copyright (C) 2009 Shen Liang <shenzhuxi@gmail.com>
20 // Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
21 // Copyright (C) 2009-2011, 2015, 2018-2021 Albert Astals Cid <aacid@kde.org>
22 // Copyright (C) 2010, 2012, 2017 Adrian Johnson <ajohnson@redneon.com>
23 // Copyright (C) 2010 Hib Eris <hib@hiberis.nl>
24 // Copyright (C) 2010 Jonathan Liu <net147@gmail.com>
25 // Copyright (C) 2010 William Bader <williambader@hotmail.com>
26 // Copyright (C) 2011-2013 Thomas Freitag <Thomas.Freitag@alfa.de>
27 // Copyright (C) 2013, 2015, 2018 Adam Reichold <adamreichold@myopera.com>
28 // Copyright (C) 2013 Suzuki Toshiya <mpsuzuki@hiroshima-u.ac.jp>
29 // Copyright (C) 2015 William Bader <williambader@hotmail.com>
30 // Copyright (C) 2018 Martin Packman <gzlist@googlemail.com>
31 // Copyright (C) 2019 Yves-Gaël Chény <gitlab@r0b0t.fr>
32 // Copyright (C) 2019-2021 Oliver Sander <oliver.sander@tu-dresden.de>
33 // Copyright (C) 2019 <corentinf@free.fr>
34 // Copyright (C) 2019 Kris Jurka <jurka@ejurka.com>
35 // Copyright (C) 2019 Sébastien Berthier <s.berthier@bee-buzziness.com>
36 // Copyright (C) 2020 Stéfan van der Walt <sjvdwalt@gmail.com>
37 // Copyright (C) 2020 Philipp Knechtges <philipp-dev@knechtges.com>
38 // Copyright (C) 2021 Diogo Kollross <diogoko@gmail.com>
39 // Copyright (C) 2021 Peter Williams <peter@newton.cx>
40 //
41 // To see a description of the changes please see the Changelog file that
42 // came with your tarball or type make ChangeLog if you are building from git
43 //
44 //========================================================================
45 
46 #include "config.h"
47 #include <poppler-config.h>
48 #if defined(_WIN32) || defined(__CYGWIN__)
49 #    include <fcntl.h> // for O_BINARY
50 #    include <io.h> // for _setmode
51 #endif
52 #include <cstdio>
53 #include <cmath>
54 #include "parseargs.h"
55 #include "goo/gmem.h"
56 #include "goo/GooString.h"
57 #include "GlobalParams.h"
58 #include "Object.h"
59 #include "PDFDoc.h"
60 #include "PDFDocFactory.h"
61 #include "splash/SplashBitmap.h"
62 #include "splash/Splash.h"
63 #include "splash/SplashErrorCodes.h"
64 #include "SplashOutputDev.h"
65 #include "Win32Console.h"
66 #include "numberofcharacters.h"
67 #include "sanitychecks.h"
68 
69 // Uncomment to build pdftoppm with pthreads
70 // You may also have to change the buildsystem to
71 // link pdftoppm to pthread library
72 // This is here for developer testing not user ready
73 // #define UTILS_USE_PTHREADS 1
74 
75 #ifdef UTILS_USE_PTHREADS
76 #    include <cerrno>
77 #    include <pthread.h>
78 #    include <deque>
79 #endif // UTILS_USE_PTHREADS
80 
81 #ifdef USE_CMS
82 #    include <lcms2.h>
83 #endif
84 
85 static int firstPage = 1;
86 static int lastPage = 0;
87 static bool printOnlyOdd = false;
88 static bool printOnlyEven = false;
89 static bool singleFile = false;
90 static bool scaleDimensionBeforeRotation = false;
91 static double resolution = 0.0;
92 static double x_resolution = 150.0;
93 static double y_resolution = 150.0;
94 static int scaleTo = 0;
95 static int x_scaleTo = 0;
96 static int y_scaleTo = 0;
97 static int param_x = 0;
98 static int param_y = 0;
99 static int param_w = 0;
100 static int param_h = 0;
101 static int sz = 0;
102 static bool hideAnnotations = false;
103 static bool useCropBox = false;
104 static bool mono = false;
105 static bool gray = false;
106 #ifdef USE_CMS
107 static GooString displayprofilename;
108 static GfxLCMSProfilePtr displayprofile;
109 static GooString defaultgrayprofilename;
110 static GfxLCMSProfilePtr defaultgrayprofile;
111 static GooString defaultrgbprofilename;
112 static GfxLCMSProfilePtr defaultrgbprofile;
113 static GooString defaultcmykprofilename;
114 static GfxLCMSProfilePtr defaultcmykprofile;
115 #endif
116 static char sep[2] = "-";
117 static bool forceNum = false;
118 static bool png = false;
119 static bool jpeg = false;
120 static bool jpegcmyk = false;
121 static bool tiff = false;
122 static GooString jpegOpt;
123 static int jpegQuality = -1;
124 static bool jpegProgressive = false;
125 static bool jpegOptimize = false;
126 static bool overprint = false;
127 static bool splashOverprintPreview = false;
128 static char enableFreeTypeStr[16] = "";
129 static bool enableFreeType = true;
130 static char antialiasStr[16] = "";
131 static char vectorAntialiasStr[16] = "";
132 static bool fontAntialias = true;
133 static bool vectorAntialias = true;
134 static char ownerPassword[33] = "";
135 static char userPassword[33] = "";
136 static char TiffCompressionStr[16] = "";
137 static char thinLineModeStr[8] = "";
138 static SplashThinLineMode thinLineMode = splashThinLineDefault;
139 #ifdef UTILS_USE_PTHREADS
140 static int numberOfJobs = 1;
141 #endif // UTILS_USE_PTHREADS
142 static bool quiet = false;
143 static bool progress = false;
144 static bool printVersion = false;
145 static bool printHelp = false;
146 
147 static const ArgDesc argDesc[] = { { "-f", argInt, &firstPage, 0, "first page to print" },
148                                    { "-l", argInt, &lastPage, 0, "last page to print" },
149                                    { "-o", argFlag, &printOnlyOdd, 0, "print only odd pages" },
150                                    { "-e", argFlag, &printOnlyEven, 0, "print only even pages" },
151                                    { "-singlefile", argFlag, &singleFile, 0, "write only the first page and do not add digits" },
152                                    { "-scale-dimension-before-rotation", argFlag, &scaleDimensionBeforeRotation, 0, "for rotated pdf, resize dimensions before the rotation" },
153 
154                                    { "-r", argFP, &resolution, 0, "resolution, in DPI (default is 150)" },
155                                    { "-rx", argFP, &x_resolution, 0, "X resolution, in DPI (default is 150)" },
156                                    { "-ry", argFP, &y_resolution, 0, "Y resolution, in DPI (default is 150)" },
157                                    { "-scale-to", argInt, &scaleTo, 0, "scales each page to fit within scale-to*scale-to pixel box" },
158                                    { "-scale-to-x", argInt, &x_scaleTo, 0, "scales each page horizontally to fit in scale-to-x pixels" },
159                                    { "-scale-to-y", argInt, &y_scaleTo, 0, "scales each page vertically to fit in scale-to-y pixels" },
160 
161                                    { "-x", argInt, &param_x, 0, "x-coordinate of the crop area top left corner" },
162                                    { "-y", argInt, &param_y, 0, "y-coordinate of the crop area top left corner" },
163                                    { "-W", argInt, &param_w, 0, "width of crop area in pixels (default is 0)" },
164                                    { "-H", argInt, &param_h, 0, "height of crop area in pixels (default is 0)" },
165                                    { "-sz", argInt, &sz, 0, "size of crop square in pixels (sets W and H)" },
166                                    { "-cropbox", argFlag, &useCropBox, 0, "use the crop box rather than media box" },
167                                    { "-hide-annotations", argFlag, &hideAnnotations, 0, "do not show annotations" },
168 
169                                    { "-mono", argFlag, &mono, 0, "generate a monochrome PBM file" },
170                                    { "-gray", argFlag, &gray, 0, "generate a grayscale PGM file" },
171 #ifdef USE_CMS
172                                    { "-displayprofile", argGooString, &displayprofilename, 0, "ICC color profile to use as the display profile" },
173                                    { "-defaultgrayprofile", argGooString, &defaultgrayprofilename, 0, "ICC color profile to use as the DefaultGray color space" },
174                                    { "-defaultrgbprofile", argGooString, &defaultrgbprofilename, 0, "ICC color profile to use as the DefaultRGB color space" },
175                                    { "-defaultcmykprofile", argGooString, &defaultcmykprofilename, 0, "ICC color profile to use as the DefaultCMYK color space" },
176 #endif
177                                    { "-sep", argString, sep, sizeof(sep), "single character separator between name and page number, default - " },
178                                    { "-forcenum", argFlag, &forceNum, 0, "force page number even if there is only one page " },
179 #ifdef ENABLE_LIBPNG
180                                    { "-png", argFlag, &png, 0, "generate a PNG file" },
181 #endif
182 #ifdef ENABLE_LIBJPEG
183                                    { "-jpeg", argFlag, &jpeg, 0, "generate a JPEG file" },
184                                    { "-jpegcmyk", argFlag, &jpegcmyk, 0, "generate a CMYK JPEG file" },
185                                    { "-jpegopt", argGooString, &jpegOpt, 0, "jpeg options, with format <opt1>=<val1>[,<optN>=<valN>]*" },
186 #endif
187                                    { "-overprint", argFlag, &overprint, 0, "enable overprint" },
188 #ifdef ENABLE_LIBTIFF
189                                    { "-tiff", argFlag, &tiff, 0, "generate a TIFF file" },
190                                    { "-tiffcompression", argString, TiffCompressionStr, sizeof(TiffCompressionStr), "set TIFF compression: none, packbits, jpeg, lzw, deflate" },
191 #endif
192                                    { "-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), "enable FreeType font rasterizer: yes, no" },
193                                    { "-thinlinemode", argString, thinLineModeStr, sizeof(thinLineModeStr), "set thin line mode: none, solid, shape. Default: none" },
194 
195                                    { "-aa", argString, antialiasStr, sizeof(antialiasStr), "enable font anti-aliasing: yes, no" },
196                                    { "-aaVector", argString, vectorAntialiasStr, sizeof(vectorAntialiasStr), "enable vector anti-aliasing: yes, no" },
197 
198                                    { "-opw", argString, ownerPassword, sizeof(ownerPassword), "owner password (for encrypted files)" },
199                                    { "-upw", argString, userPassword, sizeof(userPassword), "user password (for encrypted files)" },
200 
201 #ifdef UTILS_USE_PTHREADS
202                                    { "-j", argInt, &numberOfJobs, 0, "number of jobs to run concurrently" },
203 #endif // UTILS_USE_PTHREADS
204 
205                                    { "-q", argFlag, &quiet, 0, "don't print any messages or errors" },
206                                    { "-progress", argFlag, &progress, 0, "print progress info" },
207                                    { "-v", argFlag, &printVersion, 0, "print copyright and version info" },
208                                    { "-h", argFlag, &printHelp, 0, "print usage information" },
209                                    { "-help", argFlag, &printHelp, 0, "print usage information" },
210                                    { "--help", argFlag, &printHelp, 0, "print usage information" },
211                                    { "-?", argFlag, &printHelp, 0, "print usage information" },
212                                    {} };
213 
214 static constexpr int kOtherError = 99;
215 
needToRotate(int angle)216 static bool needToRotate(int angle)
217 {
218     return (angle == 90) || (angle == 270);
219 }
220 
parseJpegOptions()221 static bool parseJpegOptions()
222 {
223     // jpegOpt format is: <opt1>=<val1>,<opt2>=<val2>,...
224     const char *nextOpt = jpegOpt.c_str();
225     while (nextOpt && *nextOpt) {
226         const char *comma = strchr(nextOpt, ',');
227         GooString opt;
228         if (comma) {
229             opt.Set(nextOpt, comma - nextOpt);
230             nextOpt = comma + 1;
231         } else {
232             opt.Set(nextOpt);
233             nextOpt = nullptr;
234         }
235         // here opt is "<optN>=<valN> "
236         const char *equal = strchr(opt.c_str(), '=');
237         if (!equal) {
238             fprintf(stderr, "Unknown jpeg option \"%s\"\n", opt.c_str());
239             return false;
240         }
241         int iequal = equal - opt.c_str();
242         GooString value(&opt, iequal + 1, opt.getLength() - iequal - 1);
243         opt.del(iequal, opt.getLength() - iequal);
244         // here opt is "<optN>" and value is "<valN>"
245 
246         if (opt.cmp("quality") == 0) {
247             if (!isInt(value.c_str())) {
248                 fprintf(stderr, "Invalid jpeg quality\n");
249                 return false;
250             }
251             jpegQuality = atoi(value.c_str());
252             if (jpegQuality < 0 || jpegQuality > 100) {
253                 fprintf(stderr, "jpeg quality must be between 0 and 100\n");
254                 return false;
255             }
256         } else if (opt.cmp("progressive") == 0) {
257             jpegProgressive = false;
258             if (value.cmp("y") == 0) {
259                 jpegProgressive = true;
260             } else if (value.cmp("n") != 0) {
261                 fprintf(stderr, "jpeg progressive option must be \"y\" or \"n\"\n");
262                 return false;
263             }
264         } else if (opt.cmp("optimize") == 0 || opt.cmp("optimise") == 0) {
265             jpegOptimize = false;
266             if (value.cmp("y") == 0) {
267                 jpegOptimize = true;
268             } else if (value.cmp("n") != 0) {
269                 fprintf(stderr, "jpeg optimize option must be \"y\" or \"n\"\n");
270                 return false;
271             }
272         } else {
273             fprintf(stderr, "Unknown jpeg option \"%s\"\n", opt.c_str());
274             return false;
275         }
276     }
277     return true;
278 }
279 
__anond13f95900102(Annot *annot, void *user_data) 280 static auto annotDisplayDecideCbk = [](Annot *annot, void *user_data) { return !hideAnnotations; };
281 
savePageSlice(PDFDoc * doc,SplashOutputDev * splashOut,int pg,int x,int y,int w,int h,double pg_w,double pg_h,char * ppmFile)282 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)
283 {
284     if (w == 0)
285         w = (int)ceil(pg_w);
286     if (h == 0)
287         h = (int)ceil(pg_h);
288     w = (x + w > pg_w ? (int)ceil(pg_w - x) : w);
289     h = (y + h > pg_h ? (int)ceil(pg_h - y) : h);
290     doc->displayPageSlice(splashOut, pg, x_resolution, y_resolution, 0, !useCropBox, false, false, x, y, w, h, nullptr, nullptr, annotDisplayDecideCbk, nullptr);
291 
292     SplashBitmap *bitmap = splashOut->getBitmap();
293 
294     SplashBitmap::WriteImgParams params;
295     params.jpegQuality = jpegQuality;
296     params.jpegProgressive = jpegProgressive;
297     params.jpegOptimize = jpegOptimize;
298     params.tiffCompression.Set(TiffCompressionStr);
299 
300     if (ppmFile != nullptr) {
301         SplashError e;
302 
303         if (png) {
304             e = bitmap->writeImgFile(splashFormatPng, ppmFile, x_resolution, y_resolution);
305         } else if (jpeg) {
306             e = bitmap->writeImgFile(splashFormatJpeg, ppmFile, x_resolution, y_resolution, &params);
307         } else if (jpegcmyk) {
308             e = bitmap->writeImgFile(splashFormatJpegCMYK, ppmFile, x_resolution, y_resolution, &params);
309         } else if (tiff) {
310             e = bitmap->writeImgFile(splashFormatTiff, ppmFile, x_resolution, y_resolution, &params);
311         } else {
312             e = bitmap->writePNMFile(ppmFile);
313         }
314         if (e != splashOk) {
315             fprintf(stderr, "Could not write image to %s; exiting\n", ppmFile);
316             exit(EXIT_FAILURE);
317         }
318     } else {
319 #if defined(_WIN32) || defined(__CYGWIN__)
320         _setmode(fileno(stdout), O_BINARY);
321 #endif
322 
323         if (png) {
324             bitmap->writeImgFile(splashFormatPng, stdout, x_resolution, y_resolution);
325         } else if (jpeg) {
326             bitmap->writeImgFile(splashFormatJpeg, stdout, x_resolution, y_resolution, &params);
327         } else if (tiff) {
328             bitmap->writeImgFile(splashFormatTiff, stdout, x_resolution, y_resolution, &params);
329         } else {
330             bitmap->writePNMFile(stdout);
331         }
332     }
333 
334     if (progress) {
335         fprintf(stderr, "%d %d %s\n", pg, lastPage, ppmFile != nullptr ? ppmFile : "");
336     }
337 }
338 
339 #ifdef UTILS_USE_PTHREADS
340 
341 struct PageJob
342 {
343     PDFDoc *doc;
344     int pg;
345 
346     double pg_w, pg_h;
347     SplashColor *paperColor;
348 
349     char *ppmFile;
350 };
351 
352 static std::deque<PageJob> pageJobQueue;
353 static pthread_mutex_t pageJobMutex = PTHREAD_MUTEX_INITIALIZER;
354 
processPageJobs()355 static void processPageJobs()
356 {
357     while (true) {
358         // pop the next job or exit if queue is empty
359         pthread_mutex_lock(&pageJobMutex);
360 
361         if (pageJobQueue.empty()) {
362             pthread_mutex_unlock(&pageJobMutex);
363             return;
364         }
365 
366         PageJob pageJob = pageJobQueue.front();
367         pageJobQueue.pop_front();
368 
369         pthread_mutex_unlock(&pageJobMutex);
370 
371         // process the job
372         SplashOutputDev *splashOut = new SplashOutputDev(mono                              ? splashModeMono1
373                                                                  : gray                    ? splashModeMono8
374                                                                  : (jpegcmyk || overprint) ? splashModeDeviceN8
375                                                                                            : splashModeRGB8,
376                                                          4, false, *pageJob.paperColor, true, thinLineMode, splashOverprintPreview);
377         splashOut->setFontAntialias(fontAntialias);
378         splashOut->setVectorAntialias(vectorAntialias);
379         splashOut->setEnableFreeType(enableFreeType);
380 #    ifdef USE_CMS
381         splashOut->setDisplayProfile(displayprofile);
382         splashOut->setDefaultGrayProfile(defaultgrayprofile);
383         splashOut->setDefaultRGBProfile(defaultrgbprofile);
384         splashOut->setDefaultCMYKProfile(defaultcmykprofile);
385 #    endif
386         splashOut->startDoc(pageJob.doc);
387 
388         savePageSlice(pageJob.doc, splashOut, pageJob.pg, param_x, param_y, param_w, param_h, pageJob.pg_w, pageJob.pg_h, pageJob.ppmFile);
389 
390         delete splashOut;
391         delete[] pageJob.ppmFile;
392     }
393 }
394 
395 #endif // UTILS_USE_PTHREADS
396 
main(int argc,char * argv[])397 int main(int argc, char *argv[])
398 {
399     GooString *fileName = nullptr;
400     char *ppmRoot = nullptr;
401     char *ppmFile;
402     GooString *ownerPW, *userPW;
403     SplashColor paperColor;
404 #ifndef UTILS_USE_PTHREADS
405     SplashOutputDev *splashOut;
406 #else
407     pthread_t *jobs;
408 #endif // UTILS_USE_PTHREADS
409     bool ok;
410     int pg, pg_num_len;
411     double pg_w, pg_h;
412 #ifdef USE_CMS
413     cmsColorSpaceSignature profilecolorspace;
414 #endif
415 
416     Win32Console win32Console(&argc, &argv);
417 
418     // parse args
419     ok = parseArgs(argDesc, &argc, argv);
420     if (mono && gray) {
421         ok = false;
422     }
423     if (resolution != 0.0 && (x_resolution == 150.0 || y_resolution == 150.0)) {
424         x_resolution = resolution;
425         y_resolution = resolution;
426     }
427     if (!ok || argc > 3 || printVersion || printHelp) {
428         fprintf(stderr, "pdftoppm version %s\n", PACKAGE_VERSION);
429         fprintf(stderr, "%s\n", popplerCopyright);
430         fprintf(stderr, "%s\n", xpdfCopyright);
431         if (!printVersion) {
432             printUsage("pdftoppm", "[PDF-file [PPM-file-prefix]]", argDesc);
433         }
434         if (printVersion || printHelp) {
435             return 0;
436         } else {
437             return kOtherError;
438         }
439     }
440     if (argc > 1)
441         fileName = new GooString(argv[1]);
442     if (argc == 3)
443         ppmRoot = argv[2];
444 
445     if (antialiasStr[0]) {
446         if (!GlobalParams::parseYesNo2(antialiasStr, &fontAntialias)) {
447             fprintf(stderr, "Bad '-aa' value on command line\n");
448         }
449     }
450     if (vectorAntialiasStr[0]) {
451         if (!GlobalParams::parseYesNo2(vectorAntialiasStr, &vectorAntialias)) {
452             fprintf(stderr, "Bad '-aaVector' value on command line\n");
453         }
454     }
455 
456     if (jpegOpt.getLength() > 0) {
457         if (!jpeg)
458             fprintf(stderr, "Warning: -jpegopt only valid with jpeg output.\n");
459         parseJpegOptions();
460     }
461 
462     // read config file
463     globalParams = std::make_unique<GlobalParams>();
464     if (enableFreeTypeStr[0]) {
465         if (!GlobalParams::parseYesNo2(enableFreeTypeStr, &enableFreeType)) {
466             fprintf(stderr, "Bad '-freetype' value on command line\n");
467         }
468     }
469     if (thinLineModeStr[0]) {
470         if (strcmp(thinLineModeStr, "solid") == 0) {
471             thinLineMode = splashThinLineSolid;
472         } else if (strcmp(thinLineModeStr, "shape") == 0) {
473             thinLineMode = splashThinLineShape;
474         } else if (strcmp(thinLineModeStr, "none") != 0) {
475             fprintf(stderr, "Bad '-thinlinemode' value on command line\n");
476         }
477     }
478     if (quiet) {
479         globalParams->setErrQuiet(quiet);
480     }
481 
482     // open PDF file
483     if (ownerPassword[0]) {
484         ownerPW = new GooString(ownerPassword);
485     } else {
486         ownerPW = nullptr;
487     }
488     if (userPassword[0]) {
489         userPW = new GooString(userPassword);
490     } else {
491         userPW = nullptr;
492     }
493 
494     if (fileName == nullptr) {
495         fileName = new GooString("fd://0");
496     }
497     if (fileName->cmp("-") == 0) {
498         delete fileName;
499         fileName = new GooString("fd://0");
500     }
501     std::unique_ptr<PDFDoc> doc(PDFDocFactory().createPDFDoc(*fileName, ownerPW, userPW));
502     delete fileName;
503 
504     if (userPW) {
505         delete userPW;
506     }
507     if (ownerPW) {
508         delete ownerPW;
509     }
510     if (!doc->isOk()) {
511         return 1;
512     }
513 
514     // get page range
515     if (firstPage < 1)
516         firstPage = 1;
517     if (singleFile && lastPage < 1)
518         lastPage = firstPage;
519     if (lastPage < 1 || lastPage > doc->getNumPages())
520         lastPage = doc->getNumPages();
521     if (lastPage < firstPage) {
522         fprintf(stderr, "Wrong page range given: the first page (%d) can not be after the last page (%d).\n", firstPage, lastPage);
523         return kOtherError;
524     }
525 
526     // If our page range selection and document size indicate we're only
527     // outputting a single page, ensure that even/odd page selection doesn't
528     // filter out that single page.
529     if (firstPage == lastPage && ((printOnlyEven && firstPage % 2 == 1) || (printOnlyOdd && firstPage % 2 == 0))) {
530         fprintf(stderr, "Invalid even/odd page selection, no pages match criteria.\n");
531         return kOtherError;
532     }
533 
534     if (singleFile && firstPage < lastPage) {
535         if (!quiet) {
536             fprintf(stderr, "Warning: Single file will write only the first of the %d pages.\n", lastPage + 1 - firstPage);
537         }
538         lastPage = firstPage;
539     }
540 
541     // write PPM files
542     if (jpegcmyk || overprint) {
543         splashOverprintPreview = true;
544         splashClearColor(paperColor);
545     } else {
546         paperColor[0] = 255;
547         paperColor[1] = 255;
548         paperColor[2] = 255;
549     }
550 
551 #ifdef USE_CMS
552     if (!displayprofilename.toStr().empty()) {
553         displayprofile = make_GfxLCMSProfilePtr(cmsOpenProfileFromFile(displayprofilename.c_str(), "r"));
554         if (!displayprofile) {
555             fprintf(stderr, "Could not open the ICC profile \"%s\".\n", displayprofilename.c_str());
556             return kOtherError;
557         }
558         if (!cmsIsIntentSupported(displayprofile.get(), INTENT_RELATIVE_COLORIMETRIC, LCMS_USED_AS_OUTPUT) && !cmsIsIntentSupported(displayprofile.get(), INTENT_ABSOLUTE_COLORIMETRIC, LCMS_USED_AS_OUTPUT)
559             && !cmsIsIntentSupported(displayprofile.get(), INTENT_SATURATION, LCMS_USED_AS_OUTPUT) && !cmsIsIntentSupported(displayprofile.get(), INTENT_PERCEPTUAL, LCMS_USED_AS_OUTPUT)) {
560             fprintf(stderr, "ICC profile \"%s\" is not an output profile.\n", displayprofilename.c_str());
561             return kOtherError;
562         }
563         profilecolorspace = cmsGetColorSpace(displayprofile.get());
564         // Note: In contrast to pdftops we do not fail if a non-matching ICC profile is supplied.
565         //       Doing so would be pretentious, since SplashOutputDev by default assumes sRGB, even for
566         //       the CMYK and Mono cases.
567         if (jpegcmyk || overprint) {
568             if (profilecolorspace != cmsSigCmykData) {
569                 fprintf(stderr, "Warning: Supplied ICC profile \"%s\" is not a CMYK profile.\n", displayprofilename.c_str());
570             }
571         } else if (mono || gray) {
572             if (profilecolorspace != cmsSigGrayData) {
573                 fprintf(stderr, "Warning: Supplied ICC profile \"%s\" is not a monochrome profile.\n", displayprofilename.c_str());
574             }
575         } else {
576             if (profilecolorspace != cmsSigRgbData) {
577                 fprintf(stderr, "Warning: Supplied ICC profile \"%s\" is not a RGB profile.\n", displayprofilename.c_str());
578             }
579         }
580     }
581     if (!defaultgrayprofilename.toStr().empty()) {
582         defaultgrayprofile = make_GfxLCMSProfilePtr(cmsOpenProfileFromFile(defaultgrayprofilename.c_str(), "r"));
583         if (!checkICCProfile(defaultgrayprofile, defaultgrayprofilename.c_str(), LCMS_USED_AS_INPUT, cmsSigGrayData)) {
584             return kOtherError;
585         }
586     }
587     if (!defaultrgbprofilename.toStr().empty()) {
588         defaultrgbprofile = make_GfxLCMSProfilePtr(cmsOpenProfileFromFile(defaultrgbprofilename.c_str(), "r"));
589         if (!checkICCProfile(defaultrgbprofile, defaultrgbprofilename.c_str(), LCMS_USED_AS_INPUT, cmsSigRgbData)) {
590             return kOtherError;
591         }
592     }
593     if (!defaultcmykprofilename.toStr().empty()) {
594         defaultcmykprofile = make_GfxLCMSProfilePtr(cmsOpenProfileFromFile(defaultcmykprofilename.c_str(), "r"));
595         if (!checkICCProfile(defaultcmykprofile, defaultcmykprofilename.c_str(), LCMS_USED_AS_INPUT, cmsSigCmykData)) {
596             return kOtherError;
597         }
598     }
599 #endif
600 
601 #ifndef UTILS_USE_PTHREADS
602 
603     splashOut = new SplashOutputDev(mono ? splashModeMono1 : gray ? splashModeMono8 : (jpegcmyk || overprint) ? splashModeDeviceN8 : splashModeRGB8, 4, false, paperColor, true, thinLineMode, splashOverprintPreview);
604 
605     splashOut->setFontAntialias(fontAntialias);
606     splashOut->setVectorAntialias(vectorAntialias);
607     splashOut->setEnableFreeType(enableFreeType);
608 #    ifdef USE_CMS
609     splashOut->setDisplayProfile(displayprofile);
610     splashOut->setDefaultGrayProfile(defaultgrayprofile);
611     splashOut->setDefaultRGBProfile(defaultrgbprofile);
612     splashOut->setDefaultCMYKProfile(defaultcmykprofile);
613 #    endif
614     splashOut->startDoc(doc.get());
615 
616 #endif // UTILS_USE_PTHREADS
617 
618     if (sz != 0)
619         param_w = param_h = sz;
620     pg_num_len = numberOfCharacters(doc->getNumPages());
621     for (pg = firstPage; pg <= lastPage; ++pg) {
622         if (printOnlyEven && pg % 2 == 1)
623             continue;
624         if (printOnlyOdd && pg % 2 == 0)
625             continue;
626         if (useCropBox) {
627             pg_w = doc->getPageCropWidth(pg);
628             pg_h = doc->getPageCropHeight(pg);
629         } else {
630             pg_w = doc->getPageMediaWidth(pg);
631             pg_h = doc->getPageMediaHeight(pg);
632         }
633 
634         if (scaleDimensionBeforeRotation && needToRotate(doc->getPageRotate(pg)))
635             std::swap(pg_w, pg_h);
636 
637         // Handle requests for specific image size
638         if (scaleTo != 0) {
639             if (pg_w > pg_h) {
640                 resolution = (72.0 * scaleTo) / pg_w;
641                 pg_w = scaleTo;
642                 pg_h = pg_h * (resolution / 72.0);
643             } else {
644                 resolution = (72.0 * scaleTo) / pg_h;
645                 pg_h = scaleTo;
646                 pg_w = pg_w * (resolution / 72.0);
647             }
648             x_resolution = y_resolution = resolution;
649         } else {
650             if (x_scaleTo > 0) {
651                 x_resolution = (72.0 * x_scaleTo) / pg_w;
652                 pg_w = x_scaleTo;
653                 if (y_scaleTo == -1)
654                     y_resolution = x_resolution;
655             }
656 
657             if (y_scaleTo > 0) {
658                 y_resolution = (72.0 * y_scaleTo) / pg_h;
659                 pg_h = y_scaleTo;
660                 if (x_scaleTo == -1)
661                     x_resolution = y_resolution;
662             }
663 
664             // No specific image size requested---compute the size from the resolution
665             if (x_scaleTo <= 0) {
666                 pg_w = pg_w * (x_resolution / 72.0);
667             }
668             if (y_scaleTo <= 0) {
669                 pg_h = pg_h * (y_resolution / 72.0);
670             }
671         }
672 
673         if (!scaleDimensionBeforeRotation && needToRotate(doc->getPageRotate(pg)))
674             std::swap(pg_w, pg_h);
675 
676         if (ppmRoot != nullptr) {
677             const char *ext = png ? "png" : (jpeg || jpegcmyk) ? "jpg" : tiff ? "tif" : mono ? "pbm" : gray ? "pgm" : "ppm";
678             if (singleFile && !forceNum) {
679                 ppmFile = new char[strlen(ppmRoot) + 1 + strlen(ext) + 1];
680                 sprintf(ppmFile, "%s.%s", ppmRoot, ext);
681             } else {
682                 ppmFile = new char[strlen(ppmRoot) + 1 + pg_num_len + 1 + strlen(ext) + 1];
683                 sprintf(ppmFile, "%s%s%0*d.%s", ppmRoot, sep, pg_num_len, pg, ext);
684             }
685         } else {
686             ppmFile = nullptr;
687         }
688 #ifndef UTILS_USE_PTHREADS
689         // process job in main thread
690         savePageSlice(doc.get(), splashOut, pg, param_x, param_y, param_w, param_h, pg_w, pg_h, ppmFile);
691 
692         delete[] ppmFile;
693 #else
694 
695         // queue job for worker threads
696         PageJob pageJob = { .doc = doc.get(),
697                             .pg = pg,
698 
699                             .pg_w = pg_w,
700                             .pg_h = pg_h,
701 
702                             .paperColor = &paperColor,
703 
704                             .ppmFile = ppmFile };
705 
706         pageJobQueue.push_back(pageJob);
707 
708 #endif // UTILS_USE_PTHREADS
709     }
710 #ifndef UTILS_USE_PTHREADS
711     delete splashOut;
712 #else
713 
714     // spawn worker threads and wait on them
715     jobs = (pthread_t *)malloc(numberOfJobs * sizeof(pthread_t));
716 
717     for (int i = 0; i < numberOfJobs; ++i) {
718         if (pthread_create(&jobs[i], NULL, (void *(*)(void *))processPageJobs, NULL) != 0) {
719             fprintf(stderr, "pthread_create() failed with errno: %d\n", errno);
720             exit(EXIT_FAILURE);
721         }
722     }
723 
724     for (int i = 0; i < numberOfJobs; ++i) {
725         if (pthread_join(jobs[i], NULL) != 0) {
726             fprintf(stderr, "pthread_join() failed with errno: %d\n", errno);
727             exit(EXIT_FAILURE);
728         }
729     }
730 
731     free(jobs);
732 
733 #endif // UTILS_USE_PTHREADS
734 
735     return 0;
736 }
737