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

source code of poppler/utils/pdftoppm.cc