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 | |
86 | static int firstPage = 1; |
87 | static int lastPage = 0; |
88 | static bool printOnlyOdd = false; |
89 | static bool printOnlyEven = false; |
90 | static bool singleFile = false; |
91 | static bool scaleDimensionBeforeRotation = false; |
92 | static double resolution = 0.0; |
93 | static double x_resolution = 150.0; |
94 | static double y_resolution = 150.0; |
95 | static int scaleTo = 0; |
96 | static int x_scaleTo = 0; |
97 | static int y_scaleTo = 0; |
98 | static int param_x = 0; |
99 | static int param_y = 0; |
100 | static int param_w = 0; |
101 | static int param_h = 0; |
102 | static int sz = 0; |
103 | static bool hideAnnotations = false; |
104 | static bool useCropBox = false; |
105 | static bool mono = false; |
106 | static bool gray = false; |
107 | #ifdef USE_CMS |
108 | static GooString displayprofilename; |
109 | static GfxLCMSProfilePtr displayprofile; |
110 | static GooString defaultgrayprofilename; |
111 | static GfxLCMSProfilePtr defaultgrayprofile; |
112 | static GooString defaultrgbprofilename; |
113 | static GfxLCMSProfilePtr defaultrgbprofile; |
114 | static GooString defaultcmykprofilename; |
115 | static GfxLCMSProfilePtr defaultcmykprofile; |
116 | #endif |
117 | static char sep[2] = "-" ; |
118 | static bool forceNum = false; |
119 | static bool png = false; |
120 | static bool jpeg = false; |
121 | static bool jpegcmyk = false; |
122 | static bool tiff = false; |
123 | static GooString jpegOpt; |
124 | static int jpegQuality = -1; |
125 | static bool jpegProgressive = false; |
126 | static bool jpegOptimize = false; |
127 | static bool overprint = false; |
128 | static bool splashOverprintPreview = false; |
129 | static char enableFreeTypeStr[16] = "" ; |
130 | static bool enableFreeType = true; |
131 | static char antialiasStr[16] = "" ; |
132 | static char vectorAntialiasStr[16] = "" ; |
133 | static bool fontAntialias = true; |
134 | static bool vectorAntialias = true; |
135 | static char ownerPassword[33] = "" ; |
136 | static char userPassword[33] = "" ; |
137 | static char TiffCompressionStr[16] = "" ; |
138 | static char thinLineModeStr[8] = "" ; |
139 | static SplashThinLineMode thinLineMode = splashThinLineDefault; |
140 | #ifdef UTILS_USE_PTHREADS |
141 | static int numberOfJobs = 1; |
142 | #endif // UTILS_USE_PTHREADS |
143 | static bool quiet = false; |
144 | static bool progress = false; |
145 | static bool printVersion = false; |
146 | static bool printHelp = false; |
147 | |
148 | static 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: ¶m_x, .size: 0, .usage: "x-coordinate of the crop area top left corner" }, |
163 | { .arg: "-y" , .kind: argInt, .val: ¶m_y, .size: 0, .usage: "y-coordinate of the crop area top left corner" }, |
164 | { .arg: "-W" , .kind: argInt, .val: ¶m_w, .size: 0, .usage: "width of crop area in pixels (default is 0)" }, |
165 | { .arg: "-H" , .kind: argInt, .val: ¶m_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 | |
215 | static constexpr int kOtherError = 99; |
216 | |
217 | static bool needToRotate(int angle) |
218 | { |
219 | return (angle == 90) || (angle == 270); |
220 | } |
221 | |
222 | static 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 | |
281 | static auto annotDisplayDecideCbk = [](Annot *annot, void *user_data) { return !hideAnnotations; }; |
282 | |
283 | 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) |
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: ¶ms); |
310 | } else if (jpegcmyk) { |
311 | e = bitmap->writeImgFile(format: splashFormatJpegCMYK, fileName: ppmFile, hDPI: x_resolution, vDPI: y_resolution, params: ¶ms); |
312 | } else if (tiff) { |
313 | e = bitmap->writeImgFile(format: splashFormatTiff, fileName: ppmFile, hDPI: x_resolution, vDPI: y_resolution, params: ¶ms); |
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: ¶ms); |
330 | } else if (tiff) { |
331 | bitmap->writeImgFile(format: splashFormatTiff, stdout, hDPI: x_resolution, vDPI: y_resolution, params: ¶ms); |
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 | |
344 | struct PageJob |
345 | { |
346 | PDFDoc *doc; |
347 | int pg; |
348 | |
349 | double pg_w, pg_h; |
350 | SplashColor *paperColor; |
351 | |
352 | char *ppmFile; |
353 | }; |
354 | |
355 | static std::deque<PageJob> pageJobQueue; |
356 | static pthread_mutex_t pageJobMutex = PTHREAD_MUTEX_INITIALIZER; |
357 | |
358 | static 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 | |
400 | int 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 | |