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, ¶m_x, 0, "x-coordinate of the crop area top left corner" },
162 { "-y", argInt, ¶m_y, 0, "y-coordinate of the crop area top left corner" },
163 { "-W", argInt, ¶m_w, 0, "width of crop area in pixels (default is 0)" },
164 { "-H", argInt, ¶m_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
__anond96c09a70102(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, ¶ms);
307 } else if (jpegcmyk) {
308 e = bitmap->writeImgFile(splashFormatJpegCMYK, ppmFile, x_resolution, y_resolution, ¶ms);
309 } else if (tiff) {
310 e = bitmap->writeImgFile(splashFormatTiff, ppmFile, x_resolution, y_resolution, ¶ms);
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, ¶ms);
327 } else if (tiff) {
328 bitmap->writeImgFile(splashFormatTiff, stdout, x_resolution, y_resolution, ¶ms);
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