1/* Copyright Krzysztof Kowalczyk 2006-2007
2 Copyright Hib Eris <hib@hiberis.nl> 2008, 2013
3 Copyright 2018, 2020, 2022 Albert Astals Cid <aacid@kde.org> 2018
4 Copyright 2019 Oliver Sander <oliver.sander@tu-dresden.de>
5 Copyright 2020 Adam Reichold <adam.reichold@t-online.de>
6 License: GPLv2 */
7/*
8 A tool to stress-test poppler rendering and measure rendering times for
9 very simplistic performance measuring.
10
11 TODO:
12 * make it work with cairo output as well
13 * print more info about document like e.g. enumerate images,
14 streams, compression, encryption, password-protection. Each should have
15 a command-line arguments to turn it on/off
16 * never over-write file given as -out argument (optionally, provide -force
17 option to force writing the -out file). It's way too easy too lose results
18 of a previous run.
19*/
20
21#ifdef _MSC_VER
22// this sucks but I don't know any other way
23# pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
24#endif
25
26#include <config.h>
27
28#ifdef _WIN32
29# include <windows.h>
30#else
31# include <strings.h>
32#endif
33
34// Define COPY_FILE if you want the file to be copied to a local disk first
35// before it's tested. This is desired if a file is on a slow drive.
36// Currently copying only works on Windows.
37// Not enabled by default.
38// #define COPY_FILE 1
39
40#include <cassert>
41#include <cstdio>
42#include <cstdarg>
43#include <cctype>
44#include <cstdlib>
45#include <cstring>
46#include <cerrno>
47#include <ctime>
48
49#ifdef HAVE_DIRENT_H
50# include <dirent.h>
51#endif
52
53#include "Error.h"
54#include "ErrorCodes.h"
55#include "goo/GooString.h"
56#include "goo/GooTimer.h"
57#include "GlobalParams.h"
58#include "splash/SplashBitmap.h"
59#include "Object.h" /* must be included before SplashOutputDev.h because of sloppiness in SplashOutputDev.h */
60#include "SplashOutputDev.h"
61#include "TextOutputDev.h"
62#include "PDFDoc.h"
63#include "Link.h"
64
65#ifdef _MSC_VER
66# define strdup _strdup
67# define strcasecmp _stricmp
68#endif
69
70#define dimof(X) (sizeof(X) / sizeof((X)[0]))
71
72#define INVALID_PAGE_NO -1
73
74/* Those must be implemented in order to provide preview during execution.
75 They can be no-ops. An implementation for windows is in
76 perf-test-preview-win.cc
77*/
78extern void PreviewBitmapInit();
79extern void PreviewBitmapDestroy();
80extern void PreviewBitmapSplash(SplashBitmap *bmpSplash);
81
82class PdfEnginePoppler
83{
84public:
85 PdfEnginePoppler();
86 ~PdfEnginePoppler();
87
88 PdfEnginePoppler(const PdfEnginePoppler &) = delete;
89 PdfEnginePoppler &operator=(const PdfEnginePoppler &) = delete;
90
91 const char *fileName() const { return _fileName; };
92
93 void setFileName(const char *fileName)
94 {
95 assert(!_fileName);
96 _fileName = (char *)strdup(s: fileName);
97 }
98
99 int pageCount() const { return _pageCount; }
100
101 bool load(const char *fileName);
102 SplashBitmap *renderBitmap(int pageNo, double zoomReal, int rotation);
103
104 SplashOutputDev *outputDevice();
105
106private:
107 char *_fileName;
108 int _pageCount;
109
110 PDFDoc *_pdfDoc;
111 SplashOutputDev *_outputDev;
112};
113
114typedef struct StrList
115{
116 struct StrList *next;
117 char *str;
118} StrList;
119
120/* List of all command-line arguments that are not switches.
121 We assume those are:
122 - names of PDF files
123 - names of a file with a list of PDF files
124 - names of directories with PDF files
125*/
126static StrList *gArgsListRoot = nullptr;
127
128/* Names of all command-line switches we recognize */
129#define TIMINGS_ARG "-timings"
130#define RESOLUTION_ARG "-resolution"
131#define RECURSIVE_ARG "-recursive"
132#define OUT_ARG "-out"
133#define PREVIEW_ARG "-preview"
134#define SLOW_PREVIEW_ARG "-slowpreview"
135#define LOAD_ONLY_ARG "-loadonly"
136#define PAGE_ARG "-page"
137#define TEXT_ARG "-text"
138
139/* Should we record timings? True if -timings command-line argument was given. */
140static bool gfTimings = false;
141
142/* If true, we use render each page at resolution 'gResolutionX'/'gResolutionY'.
143 If false, we render each page at its native resolution.
144 True if -resolution NxM command-line argument was given. */
145static bool gfForceResolution = false;
146static int gResolutionX = 0;
147static int gResolutionY = 0;
148/* If NULL, we output the log info to stdout. If not NULL, should be a name
149 of the file to which we output log info.
150 Controlled by -out command-line argument. */
151static char *gOutFileName = nullptr;
152/* FILE * corresponding to gOutFileName or stdout if gOutFileName is NULL or
153 was invalid name */
154static FILE *gOutFile = nullptr;
155/* FILE * corresponding to gOutFileName or stderr if gOutFileName is NULL or
156 was invalid name */
157static FILE *gErrFile = nullptr;
158
159/* If True and a directory is given as a command-line argument, we'll process
160 pdf files in sub-directories as well.
161 Controlled by -recursive command-line argument */
162static bool gfRecursive = false;
163
164/* If true, preview rendered image. To make sure that they're being rendered correctly. */
165static bool gfPreview = false;
166
167/* 1 second (1000 milliseconds) */
168#define SLOW_PREVIEW_TIME 1000
169
170/* If true, preview rendered image in a slow mode i.e. delay displaying for
171 SLOW_PREVIEW_TIME. This is so that a human has enough time to see if the
172 PDF renders ok. In release mode on fast processor pages take only ~100-200 ms
173 to render and they go away too quickly to be inspected by a human. */
174static bool gfSlowPreview = false;
175
176/* If true, we only dump the text, not render */
177static bool gfTextOnly = false;
178
179#define PAGE_NO_NOT_GIVEN -1
180
181/* If equals PAGE_NO_NOT_GIVEN, we're in default mode where we render all pages.
182 If different, will only render this page */
183static int gPageNo = PAGE_NO_NOT_GIVEN;
184/* If true, will only load the file, not render any pages. Mostly for
185 profiling load time */
186static bool gfLoadOnly = false;
187
188#define PDF_FILE_DPI 72
189
190#define MAX_FILENAME_SIZE 1024
191
192/* DOS is 0xd 0xa */
193#define DOS_NEWLINE "\x0d\x0a"
194/* Mac is single 0xd */
195#define MAC_NEWLINE "\x0d"
196/* Unix is single 0xa (10) */
197#define UNIX_NEWLINE "\x0a"
198#define UNIX_NEWLINE_C 0xa
199
200static void memzero(void *data, size_t len)
201{
202 memset(s: data, c: 0, n: len);
203}
204
205static void *zmalloc(size_t len)
206{
207 void *data = malloc(size: len);
208 if (data) {
209 memzero(data, len);
210 }
211 return data;
212}
213
214/* Concatenate 4 strings. Any string can be NULL.
215 Caller needs to free() memory. */
216static char *str_cat4(const char *str1, const char *str2, const char *str3, const char *str4)
217{
218 char *str;
219 char *tmp;
220 size_t str1_len = 0;
221 size_t str2_len = 0;
222 size_t str3_len = 0;
223 size_t str4_len = 0;
224
225 if (str1) {
226 str1_len = strlen(s: str1);
227 }
228 if (str2) {
229 str2_len = strlen(s: str2);
230 }
231 if (str3) {
232 str3_len = strlen(s: str3);
233 }
234 if (str4) {
235 str4_len = strlen(s: str4);
236 }
237
238 str = (char *)zmalloc(len: str1_len + str2_len + str3_len + str4_len + 1);
239 if (!str) {
240 return nullptr;
241 }
242
243 tmp = str;
244 if (str1) {
245 memcpy(dest: tmp, src: str1, n: str1_len);
246 tmp += str1_len;
247 }
248 if (str2) {
249 memcpy(dest: tmp, src: str2, n: str2_len);
250 tmp += str2_len;
251 }
252 if (str3) {
253 memcpy(dest: tmp, src: str3, n: str3_len);
254 tmp += str3_len;
255 }
256 if (str4) {
257 memcpy(dest: tmp, src: str4, n: str1_len);
258 }
259 return str;
260}
261
262static char *str_dup(const char *str)
263{
264 return str_cat4(str1: str, str2: nullptr, str3: nullptr, str4: nullptr);
265}
266
267static bool str_eq(const char *str1, const char *str2)
268{
269 if (!str1 && !str2) {
270 return true;
271 }
272 if (!str1 || !str2) {
273 return false;
274 }
275 if (0 == strcmp(s1: str1, s2: str2)) {
276 return true;
277 }
278 return false;
279}
280
281static bool str_ieq(const char *str1, const char *str2)
282{
283 if (!str1 && !str2) {
284 return true;
285 }
286 if (!str1 || !str2) {
287 return false;
288 }
289 if (0 == strcasecmp(s1: str1, s2: str2)) {
290 return true;
291 }
292 return false;
293}
294
295static bool str_endswith(const char *txt, const char *end)
296{
297 size_t end_len;
298 size_t txt_len;
299
300 if (!txt || !end) {
301 return false;
302 }
303
304 txt_len = strlen(s: txt);
305 end_len = strlen(s: end);
306 if (end_len > txt_len) {
307 return false;
308 }
309 if (str_eq(str1: txt + txt_len - end_len, str2: end)) {
310 return true;
311 }
312 return false;
313}
314
315/* TODO: probably should move to some other file and change name to
316 sleep_milliseconds */
317static void sleep_milliseconds(int milliseconds)
318{
319#ifdef _WIN32
320 Sleep((DWORD)milliseconds);
321#else
322 struct timespec tv;
323 int secs, nanosecs;
324 secs = milliseconds / 1000;
325 nanosecs = (milliseconds - (secs * 1000)) * 1000;
326 tv.tv_sec = (time_t)secs;
327 tv.tv_nsec = (long)nanosecs;
328 while (true) {
329 int rval = nanosleep(requested_time: &tv, remaining: &tv);
330 if (rval == 0) {
331 /* Completed the entire sleep time; all done. */
332 return;
333 } else if (errno == EINTR) {
334 /* Interrupted by a signal. Try again. */
335 continue;
336 } else {
337 /* Some other error; bail out. */
338 return;
339 }
340 }
341 return;
342#endif
343}
344
345static SplashColorMode gSplashColorMode = splashModeBGR8;
346
347static SplashColor splashColRed;
348static SplashColor splashColGreen;
349static SplashColor splashColBlue;
350static SplashColor splashColWhite;
351static SplashColor splashColBlack;
352
353#define SPLASH_COL_RED_PTR (SplashColorPtr) & (splashColRed[0])
354#define SPLASH_COL_GREEN_PTR (SplashColorPtr) & (splashColGreen[0])
355#define SPLASH_COL_BLUE_PTR (SplashColorPtr) & (splashColBlue[0])
356#define SPLASH_COL_WHITE_PTR (SplashColorPtr) & (splashColWhite[0])
357#define SPLASH_COL_BLACK_PTR (SplashColorPtr) & (splashColBlack[0])
358
359static SplashColorPtr gBgColor = SPLASH_COL_WHITE_PTR;
360
361static void splashColorSet(SplashColorPtr col, unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha)
362{
363 switch (gSplashColorMode) {
364 case splashModeBGR8:
365 col[0] = blue;
366 col[1] = green;
367 col[2] = red;
368 break;
369 case splashModeRGB8:
370 col[0] = red;
371 col[1] = green;
372 col[2] = blue;
373 break;
374 default:
375 assert(0);
376 break;
377 }
378}
379
380static void SplashColorsInit()
381{
382 splashColorSet(SPLASH_COL_RED_PTR, red: 0xff, green: 0, blue: 0, alpha: 0);
383 splashColorSet(SPLASH_COL_GREEN_PTR, red: 0, green: 0xff, blue: 0, alpha: 0);
384 splashColorSet(SPLASH_COL_BLUE_PTR, red: 0, green: 0, blue: 0xff, alpha: 0);
385 splashColorSet(SPLASH_COL_BLACK_PTR, red: 0, green: 0, blue: 0, alpha: 0);
386 splashColorSet(SPLASH_COL_WHITE_PTR, red: 0xff, green: 0xff, blue: 0xff, alpha: 0);
387}
388
389PdfEnginePoppler::PdfEnginePoppler() : _fileName(nullptr), _pageCount(INVALID_PAGE_NO), _pdfDoc(nullptr), _outputDev(nullptr) { }
390
391PdfEnginePoppler::~PdfEnginePoppler()
392{
393 free(ptr: _fileName);
394 delete _outputDev;
395 delete _pdfDoc;
396}
397
398bool PdfEnginePoppler::load(const char *fileName)
399{
400 setFileName(fileName);
401
402 _pdfDoc = new PDFDoc(std::make_unique<GooString>(args&: fileName));
403 if (!_pdfDoc->isOk()) {
404 return false;
405 }
406 _pageCount = _pdfDoc->getNumPages();
407 return true;
408}
409
410SplashOutputDev *PdfEnginePoppler::outputDevice()
411{
412 if (!_outputDev) {
413 bool bitmapTopDown = true;
414 _outputDev = new SplashOutputDev(gSplashColorMode, 4, false, gBgColor, bitmapTopDown);
415 if (_outputDev) {
416 _outputDev->startDoc(docA: _pdfDoc);
417 }
418 }
419 return _outputDev;
420}
421
422SplashBitmap *PdfEnginePoppler::renderBitmap(int pageNo, double zoomReal, int rotation)
423{
424 assert(outputDevice());
425 if (!outputDevice()) {
426 return nullptr;
427 }
428
429 double hDPI = (double)PDF_FILE_DPI * zoomReal * 0.01;
430 double vDPI = (double)PDF_FILE_DPI * zoomReal * 0.01;
431 bool useMediaBox = false;
432 bool crop = true;
433 bool doLinks = true;
434 _pdfDoc->displayPage(out: _outputDev, page: pageNo, hDPI, vDPI, rotate: rotation, useMediaBox, crop, printing: doLinks, abortCheckCbk: nullptr, abortCheckCbkData: nullptr);
435
436 SplashBitmap *bmp = _outputDev->takeBitmap();
437 return bmp;
438}
439
440static int StrList_Len(StrList **root)
441{
442 int len = 0;
443 StrList *cur;
444 assert(root);
445 if (!root) {
446 return 0;
447 }
448 cur = *root;
449 while (cur) {
450 ++len;
451 cur = cur->next;
452 }
453 return len;
454}
455
456static int StrList_InsertAndOwn(StrList **root, char *txt)
457{
458 StrList *el;
459 assert(root && txt);
460 if (!root || !txt) {
461 return false;
462 }
463
464 el = (StrList *)malloc(size: sizeof(StrList));
465 if (!el) {
466 return false;
467 }
468 el->str = txt;
469 el->next = *root;
470 *root = el;
471 return true;
472}
473
474static int StrList_Insert(StrList **root, char *txt)
475{
476 char *txtDup;
477
478 assert(root && txt);
479 if (!root || !txt) {
480 return false;
481 }
482 txtDup = str_dup(str: txt);
483 if (!txtDup) {
484 return false;
485 }
486
487 if (!StrList_InsertAndOwn(root, txt: txtDup)) {
488 free(ptr: (void *)txtDup);
489 return false;
490 }
491 return true;
492}
493
494static void StrList_FreeElement(StrList *el)
495{
496 if (!el) {
497 return;
498 }
499 free(ptr: (void *)el->str);
500 free(ptr: (void *)el);
501}
502
503static void StrList_Destroy(StrList **root)
504{
505 StrList *cur;
506 StrList *next;
507
508 if (!root) {
509 return;
510 }
511 cur = *root;
512 while (cur) {
513 next = cur->next;
514 StrList_FreeElement(el: cur);
515 cur = next;
516 }
517 *root = nullptr;
518}
519
520static void my_error(ErrorCategory, Goffset pos, const char *msg)
521{
522#if 0
523 char buf[4096], *p = buf;
524
525 // NB: this can be called before the globalParams object is created
526 if (globalParams && globalParams->getErrQuiet()) {
527 return;
528 }
529
530 if (pos >= 0) {
531 p += _snprintf(p, sizeof(buf)-1, "Error (%lld): ", (long long)pos);
532 *p = '\0';
533 OutputDebugString(p);
534 } else {
535 OutputDebugString("Error: ");
536 }
537
538 p = buf;
539 p += vsnprintf(p, sizeof(buf) - 1, msg, args);
540 while ( p > buf && isspace(p[-1]) )
541 *--p = '\0';
542 *p++ = '\r';
543 *p++ = '\n';
544 *p = '\0';
545 OutputDebugString(buf);
546
547 if (pos >= 0) {
548 p += _snprintf(p, sizeof(buf)-1, "Error (%lld): ", (long long)pos);
549 *p = '\0';
550 OutputDebugString(buf);
551 if (gErrFile)
552 fprintf(gErrFile, buf);
553 } else {
554 OutputDebugString("Error: ");
555 if (gErrFile)
556 fprintf(gErrFile, "Error: ");
557 }
558#endif
559#if 0
560 p = buf;
561 va_start(args, msg);
562 p += vsnprintf(p, sizeof(buf) - 3, msg, args);
563 while ( p > buf && isspace(p[-1]) )
564 *--p = '\0';
565 *p++ = '\r';
566 *p++ = '\n';
567 *p = '\0';
568 OutputDebugString(buf);
569 if (gErrFile)
570 fprintf(gErrFile, buf);
571 va_end(args);
572#endif
573}
574
575static void LogInfo(const char *fmt, ...) GCC_PRINTF_FORMAT(1, 2);
576
577static void LogInfo(const char *fmt, ...)
578{
579 va_list args;
580 char buf[4096], *p = buf;
581
582 p = buf;
583 va_start(args, fmt);
584 p += vsnprintf(s: p, maxlen: sizeof(buf) - 1, format: fmt, arg: args);
585 *p = '\0';
586 fprintf(stream: gOutFile, format: "%s", buf);
587 va_end(args);
588 fflush(stream: gOutFile);
589}
590
591static void PrintUsageAndExit(int argc, char **argv)
592{
593 printf(format: "Usage: pdftest [-preview|-slowpreview] [-loadonly] [-timings] [-text] [-resolution NxM] [-recursive] [-page N] [-out out.txt] pdf-files-to-process\n");
594 for (int i = 0; i < argc; i++) {
595 printf(format: "i=%d, '%s'\n", i, argv[i]);
596 }
597 exit(status: 0);
598}
599
600static bool ShowPreview()
601{
602 if (gfPreview || gfSlowPreview) {
603 return true;
604 }
605 return false;
606}
607
608static void RenderPdfAsText(const char *fileName)
609{
610 PDFDoc *pdfDoc = nullptr;
611 GooString *txt = nullptr;
612 int pageCount;
613 double timeInMs;
614
615 assert(fileName);
616 if (!fileName) {
617 return;
618 }
619
620 LogInfo(fmt: "started: %s\n", fileName);
621
622 TextOutputDev *textOut = new TextOutputDev(nullptr, true, 0, false, false);
623 if (!textOut->isOk()) {
624 delete textOut;
625 return;
626 }
627
628 GooTimer msTimer;
629 pdfDoc = new PDFDoc(std::make_unique<GooString>(args&: fileName));
630 if (!pdfDoc->isOk()) {
631 error(category: errIO, pos: -1, msg: "RenderPdfFile(): failed to open PDF file {0:s}\n", fileName);
632 goto Exit;
633 }
634
635 msTimer.stop();
636 timeInMs = msTimer.getElapsed();
637 LogInfo(fmt: "load: %.2f ms\n", timeInMs);
638
639 pageCount = pdfDoc->getNumPages();
640 LogInfo(fmt: "page count: %d\n", pageCount);
641
642 for (int curPage = 1; curPage <= pageCount; curPage++) {
643 if ((gPageNo != PAGE_NO_NOT_GIVEN) && (gPageNo != curPage)) {
644 continue;
645 }
646
647 msTimer.start();
648 int rotate = 0;
649 bool useMediaBox = false;
650 bool crop = true;
651 bool doLinks = false;
652 pdfDoc->displayPage(out: textOut, page: curPage, hDPI: 72, vDPI: 72, rotate, useMediaBox, crop, printing: doLinks);
653 txt = textOut->getText(xMin: 0.0, yMin: 0.0, xMax: 10000.0, yMax: 10000.0);
654 msTimer.stop();
655 timeInMs = msTimer.getElapsed();
656 if (gfTimings) {
657 LogInfo(fmt: "page %d: %.2f ms\n", curPage, timeInMs);
658 }
659 printf(format: "%s\n", txt->c_str());
660 delete txt;
661 txt = nullptr;
662 }
663
664Exit:
665 LogInfo(fmt: "finished: %s\n", fileName);
666 delete textOut;
667 delete pdfDoc;
668}
669
670#ifdef _MSC_VER
671# define POPPLER_TMP_NAME "c:\\poppler_tmp.pdf"
672#else
673# define POPPLER_TMP_NAME "/tmp/poppler_tmp.pdf"
674#endif
675
676static void RenderPdf(const char *fileName)
677{
678 const char *fileNameSplash = nullptr;
679 PdfEnginePoppler *engineSplash = nullptr;
680 int pageCount;
681 double timeInMs;
682
683#ifdef COPY_FILE
684 // TODO: fails if file already exists and has read-only attribute
685 CopyFile(fileName, POPPLER_TMP_NAME, false);
686 fileNameSplash = POPPLER_TMP_NAME;
687#else
688 fileNameSplash = fileName;
689#endif
690 LogInfo(fmt: "started: %s\n", fileName);
691
692 engineSplash = new PdfEnginePoppler();
693
694 GooTimer msTimer;
695 if (!engineSplash->load(fileName: fileNameSplash)) {
696 LogInfo(fmt: "failed to load splash\n");
697 goto Error;
698 }
699 msTimer.stop();
700 timeInMs = msTimer.getElapsed();
701 LogInfo(fmt: "load splash: %.2f ms\n", timeInMs);
702 pageCount = engineSplash->pageCount();
703
704 LogInfo(fmt: "page count: %d\n", pageCount);
705 if (gfLoadOnly) {
706 goto Error;
707 }
708
709 for (int curPage = 1; curPage <= pageCount; curPage++) {
710 if ((gPageNo != PAGE_NO_NOT_GIVEN) && (gPageNo != curPage)) {
711 continue;
712 }
713
714 SplashBitmap *bmpSplash = nullptr;
715
716 GooTimer msRenderTimer;
717 bmpSplash = engineSplash->renderBitmap(pageNo: curPage, zoomReal: 100.0, rotation: 0);
718 msRenderTimer.stop();
719 timeInMs = msRenderTimer.getElapsed();
720 if (gfTimings) {
721 if (!bmpSplash) {
722 LogInfo(fmt: "page splash %d: failed to render\n", curPage);
723 } else {
724 LogInfo(fmt: "page splash %d (%dx%d): %.2f ms\n", curPage, bmpSplash->getWidth(), bmpSplash->getHeight(), timeInMs);
725 }
726 }
727
728 if (ShowPreview()) {
729 PreviewBitmapSplash(bmpSplash);
730 if (gfSlowPreview) {
731 sleep_milliseconds(SLOW_PREVIEW_TIME);
732 }
733 }
734 delete bmpSplash;
735 }
736Error:
737 delete engineSplash;
738 LogInfo(fmt: "finished: %s\n", fileName);
739}
740
741static void RenderFile(const char *fileName)
742{
743 if (gfTextOnly) {
744 RenderPdfAsText(fileName);
745 return;
746 }
747
748 RenderPdf(fileName);
749}
750
751static bool ParseInteger(const char *start, const char *end, int *intOut)
752{
753 char numBuf[16];
754 int digitsCount;
755 const char *tmp;
756
757 assert(start && end && intOut);
758 assert(end >= start);
759 if (!start || !end || !intOut || (start > end)) {
760 return false;
761 }
762
763 digitsCount = 0;
764 tmp = start;
765 while (tmp <= end) {
766 if (isspace(*tmp)) {
767 /* do nothing, we allow whitespace */
768 } else if (!isdigit(*tmp)) {
769 return false;
770 }
771 numBuf[digitsCount] = *tmp;
772 ++digitsCount;
773 if (digitsCount == dimof(numBuf) - 3) { /* -3 to be safe */
774 return false;
775 }
776 ++tmp;
777 }
778 if (0 == digitsCount) {
779 return false;
780 }
781 numBuf[digitsCount] = 0;
782 *intOut = atoi(nptr: numBuf);
783 return true;
784}
785
786/* Given 'resolutionString' in format NxM (e.g. "100x200"), parse the string and put N
787 into 'resolutionXOut' and M into 'resolutionYOut'.
788 Return false if there was an error (e.g. string is not in the right format */
789static bool ParseResolutionString(const char *resolutionString, int *resolutionXOut, int *resolutionYOut)
790{
791 const char *posOfX;
792
793 assert(resolutionString);
794 assert(resolutionXOut);
795 assert(resolutionYOut);
796 if (!resolutionString || !resolutionXOut || !resolutionYOut) {
797 return false;
798 }
799 *resolutionXOut = 0;
800 *resolutionYOut = 0;
801 posOfX = strchr(s: resolutionString, c: 'X');
802 if (!posOfX) {
803 posOfX = strchr(s: resolutionString, c: 'x');
804 }
805 if (!posOfX) {
806 return false;
807 }
808 if (posOfX == resolutionString) {
809 return false;
810 }
811 if (!ParseInteger(start: resolutionString, end: posOfX - 1, intOut: resolutionXOut)) {
812 return false;
813 }
814 if (!ParseInteger(start: posOfX + 1, end: resolutionString + strlen(s: resolutionString) - 1, intOut: resolutionYOut)) {
815 return false;
816 }
817 return true;
818}
819
820static void ParseCommandLine(int argc, char **argv)
821{
822 char *arg;
823
824 if (argc < 2) {
825 PrintUsageAndExit(argc, argv);
826 }
827
828 for (int i = 1; i < argc; i++) {
829 arg = argv[i];
830 assert(arg);
831 if ('-' == arg[0]) {
832 if (str_ieq(str1: arg, TIMINGS_ARG)) {
833 gfTimings = true;
834 } else if (str_ieq(str1: arg, RESOLUTION_ARG)) {
835 ++i;
836 if (i == argc) {
837 PrintUsageAndExit(argc, argv); /* expect a file name after that */
838 }
839 if (!ParseResolutionString(resolutionString: argv[i], resolutionXOut: &gResolutionX, resolutionYOut: &gResolutionY)) {
840 PrintUsageAndExit(argc, argv);
841 }
842 gfForceResolution = true;
843 } else if (str_ieq(str1: arg, RECURSIVE_ARG)) {
844 gfRecursive = true;
845 } else if (str_ieq(str1: arg, OUT_ARG)) {
846 /* expect a file name after that */
847 ++i;
848 if (i == argc) {
849 PrintUsageAndExit(argc, argv);
850 }
851 gOutFileName = str_dup(str: argv[i]);
852 } else if (str_ieq(str1: arg, PREVIEW_ARG)) {
853 gfPreview = true;
854 } else if (str_ieq(str1: arg, TEXT_ARG)) {
855 gfTextOnly = true;
856 } else if (str_ieq(str1: arg, SLOW_PREVIEW_ARG)) {
857 gfSlowPreview = true;
858 } else if (str_ieq(str1: arg, LOAD_ONLY_ARG)) {
859 gfLoadOnly = true;
860 } else if (str_ieq(str1: arg, PAGE_ARG)) {
861 /* expect an integer after that */
862 ++i;
863 if (i == argc) {
864 PrintUsageAndExit(argc, argv);
865 }
866 gPageNo = atoi(nptr: argv[i]);
867 if (gPageNo < 1) {
868 PrintUsageAndExit(argc, argv);
869 }
870 } else {
871 /* unknown option */
872 PrintUsageAndExit(argc, argv);
873 }
874 } else {
875 /* we assume that this is not an option hence it must be
876 a name of PDF/directory/file with PDF names */
877 StrList_Insert(root: &gArgsListRoot, txt: arg);
878 }
879 }
880}
881
882static bool IsPdfFileName(char *path)
883{
884 if (str_endswith(txt: path, end: ".pdf")) {
885 return true;
886 }
887 return false;
888}
889
890/* Render 'cmdLineArg', which can be:
891 - name of PDF file
892*/
893static void RenderCmdLineArg(char *cmdLineArg)
894{
895 assert(cmdLineArg);
896 if (!cmdLineArg) {
897 return;
898 }
899 if (IsPdfFileName(path: cmdLineArg)) {
900 RenderFile(fileName: cmdLineArg);
901 } else {
902 error(category: errCommandLine, pos: -1, msg: "unexpected argument '{0:s}'", cmdLineArg);
903 }
904}
905
906int main(int argc, char **argv)
907{
908 setErrorCallback(my_error);
909 ParseCommandLine(argc, argv);
910 if (0 == StrList_Len(root: &gArgsListRoot)) {
911 PrintUsageAndExit(argc, argv);
912 }
913 assert(gArgsListRoot);
914
915 SplashColorsInit();
916 globalParams = std::make_unique<GlobalParams>();
917 if (!globalParams) {
918 return 1;
919 }
920 globalParams->setErrQuiet(false);
921
922 FILE *outFile = nullptr;
923 if (gOutFileName) {
924 outFile = fopen(filename: gOutFileName, modes: "wb");
925 if (!outFile) {
926 printf(format: "failed to open -out file %s\n", gOutFileName);
927 return 1;
928 }
929 gOutFile = outFile;
930 } else {
931 gOutFile = stdout;
932 }
933
934 if (gOutFileName) {
935 gErrFile = outFile;
936 } else {
937 gErrFile = stderr;
938 }
939
940 PreviewBitmapInit();
941
942 StrList *curr = gArgsListRoot;
943 while (curr) {
944 RenderCmdLineArg(cmdLineArg: curr->str);
945 curr = curr->next;
946 }
947 if (outFile) {
948 fclose(stream: outFile);
949 }
950 PreviewBitmapDestroy();
951 StrList_Destroy(root: &gArgsListRoot);
952 free(ptr: gOutFileName);
953 return 0;
954}
955

source code of poppler/test/perf-test.cc