warning: "_FILE_OFFSET_BITS" redefined

Bug #86870
diff --git a/test/perf-test.cc b/test/perf-test.cc
index 04adec3..4fb15d9 100644
--- a/test/perf-test.cc
+++ b/test/perf-test.cc
@@ -1,1279 +1,1280 @@
-/* Copyright Krzysztof Kowalczyk 2006-2007

-   Copyright Hib Eris <hib@hiberis.nl> 2008, 2013

-   License: GPLv2 */

-/*

-  A tool to stress-test poppler rendering and measure rendering times for

-  very simplistic performance measuring.

-

-  TODO:

-   * make it work with cairo output as well

-   * print more info about document like e.g. enumarate images,

-     streams, compression, encryption, password-protection. Each should have

-     a command-line arguments to turn it on/off

-   * never over-write file given as -out argument (optionally, provide -force

-     option to force writing the -out file). It's way too easy too lose results

-     of a previous run.

-*/

-

-#ifdef _MSC_VER

-// this sucks but I don't know any other way

-#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")

-#endif

-

-#ifdef _WIN32

-#include <windows.h>

-#else

-#include <strings.h>

-#endif

-

-// Define COPY_FILE if you want the file to be copied to a local disk first

-// before it's tested. This is desired if a file is on a slow drive.

-// Currently copying only works on Windows.

-// Not enabled by default.

-//#define COPY_FILE 1

-

-#include <assert.h>

-#include <config.h>

-#include <stdio.h>

-#include <stdarg.h>

-#include <ctype.h>

-#include <stdlib.h>

-#include <string.h>

-#include <errno.h>

-#include <time.h>

-

-#ifdef HAVE_DIRENT_H

-#include <dirent.h>

-#endif

-

-#include "Error.h"

-#include "ErrorCodes.h"

-#include "goo/GooString.h"

-#include "goo/GooList.h"

-#include "goo/GooTimer.h"

-#include "GlobalParams.h"

-#include "splash/SplashBitmap.h"

-#include "Object.h" /* must be included before SplashOutputDev.h because of sloppiness in SplashOutputDev.h */

-#include "SplashOutputDev.h"

-#include "TextOutputDev.h"

-#include "PDFDoc.h"

-#include "Link.h"

-

-#ifdef _MSC_VER

-#define strdup _strdup

-#define strcasecmp _stricmp

-#endif

-

-#define dimof(X)    (sizeof(X)/sizeof((X)[0]))

-

-#define INVALID_PAGE_NO     -1

-

-/* Those must be implemented in order to provide preview during execution.

-   They can be no-ops. An implementation for windows is in

-   perf-test-preview-win.cc

-*/

-extern void PreviewBitmapInit(void);

-extern void PreviewBitmapDestroy(void);

-extern void PreviewBitmapSplash(SplashBitmap *bmpSplash);

-

-class PdfEnginePoppler {

-public:

-    PdfEnginePoppler();

-    ~PdfEnginePoppler();

-

-    const char *fileName(void) const { return _fileName; };

-

-    void setFileName(const char *fileName) {

-        assert(!_fileName);

-        _fileName = (char*)strdup(fileName);

-    }

-

-    int pageCount(void) const { return _pageCount; }

-

-    bool load(const char *fileName);

-    SplashBitmap *renderBitmap(int pageNo, double zoomReal, int rotation);

-

-    SplashOutputDev *   outputDevice();

-private:

-    char *              _fileName;

-    int                 _pageCount;

-

-    PDFDoc *            _pdfDoc;

-    SplashOutputDev *   _outputDev;

-};

-

-typedef struct StrList {

-    struct StrList *next;

-    char *          str;

-} StrList;

-

-/* List of all command-line arguments that are not switches.

-   We assume those are:

-     - names of PDF files

-     - names of a file with a list of PDF files

-     - names of directories with PDF files

-*/

-static StrList *gArgsListRoot = NULL;

-

-/* Names of all command-line switches we recognize */

-#define TIMINGS_ARG         "-timings"

-#define RESOLUTION_ARG      "-resolution"

-#define RECURSIVE_ARG       "-recursive"

-#define OUT_ARG             "-out"

-#define PREVIEW_ARG         "-preview"

-#define SLOW_PREVIEW_ARG    "-slowpreview"

-#define LOAD_ONLY_ARG       "-loadonly"

-#define PAGE_ARG            "-page"

-#define TEXT_ARG            "-text"

-

-/* Should we record timings? True if -timings command-line argument was given. */

-static bool gfTimings = false;

-

-/* If true, we use render each page at resolution 'gResolutionX'/'gResolutionY'.

-   If false, we render each page at its native resolution.

-   True if -resolution NxM command-line argument was given. */

-static bool gfForceResolution = false;

-static int  gResolutionX = 0;

-static int  gResolutionY = 0;

-/* If NULL, we output the log info to stdout. If not NULL, should be a name

-   of the file to which we output log info.

-   Controled by -out command-line argument. */

-static char *   gOutFileName = NULL;

-/* FILE * correspondig to gOutFileName or stdout if gOutFileName is NULL or

-   was invalid name */

-static FILE *   gOutFile = NULL;

-/* FILE * correspondig to gOutFileName or stderr if gOutFileName is NULL or

-   was invalid name */

-static FILE *   gErrFile = NULL;

-

-/* If True and a directory is given as a command-line argument, we'll process

-   pdf files in sub-directories as well.

-   Controlled by -recursive command-line argument */

-static bool gfRecursive = false;

-

-/* If true, preview rendered image. To make sure that they're being rendered correctly. */

-static bool gfPreview = false;

-

-/* 1 second (1000 milliseconds) */

-#define SLOW_PREVIEW_TIME 1000

-

-/* If true, preview rendered image in a slow mode i.e. delay displaying for

-   SLOW_PREVIEW_TIME. This is so that a human has enough time to see if the

-   PDF renders ok. In release mode on fast processor pages take only ~100-200 ms

-   to render and they go away too quickly to be inspected by a human. */

-static bool gfSlowPreview = false;

-

-/* If true, we only dump the text, not render */

-static bool gfTextOnly = false;

-

-#define PAGE_NO_NOT_GIVEN -1

-

-/* If equals PAGE_NO_NOT_GIVEN, we're in default mode where we render all pages.

-   If different, will only render this page */

-static int  gPageNo = PAGE_NO_NOT_GIVEN;

-/* If true, will only load the file, not render any pages. Mostly for

-   profiling load time */

-static bool gfLoadOnly = false;

-

-#define PDF_FILE_DPI 72

-

-#define MAX_FILENAME_SIZE 1024

-

-/* DOS is 0xd 0xa */

-#define DOS_NEWLINE "\x0d\x0a"

-/* Mac is single 0xd */

-#define MAC_NEWLINE "\x0d"

-/* Unix is single 0xa (10) */

-#define UNIX_NEWLINE "\x0a"

-#define UNIX_NEWLINE_C 0xa

-

-#ifdef _WIN32

-  #define DIR_SEP_CHAR '\\'

-  #define DIR_SEP_STR  "\\"

-#else

-  #define DIR_SEP_CHAR '/'

-  #define DIR_SEP_STR  "/"

-#endif

-

-void memzero(void *data, size_t len)

-{

-    memset(data, 0, len);

-}

-

-void *zmalloc(size_t len)

-{

-    void *data = malloc(len);

-    if (data)

-        memzero(data, len);

-    return data;

-}

-

-/* Concatenate 4 strings. Any string can be NULL.

-   Caller needs to free() memory. */

-char *str_cat4(const char *str1, const char *str2, const char *str3, const char *str4)

-{

-    char *str;

-    char *tmp;

-    size_t str1_len = 0;

-    size_t str2_len = 0;

-    size_t str3_len = 0;

-    size_t str4_len = 0;

-

-    if (str1)

-        str1_len = strlen(str1);

-    if (str2)

-        str2_len = strlen(str2);

-    if (str3)

-        str3_len = strlen(str3);

-    if (str4)

-        str4_len = strlen(str4);

-

-    str = (char*)zmalloc(str1_len + str2_len + str3_len + str4_len + 1);

-    if (!str)

-        return NULL;

-

-    tmp = str;

-    if (str1) {

-        memcpy(tmp, str1, str1_len);

-        tmp += str1_len;

-    }

-    if (str2) {

-        memcpy(tmp, str2, str2_len);

-        tmp += str2_len;

-    }

-    if (str3) {

-        memcpy(tmp, str3, str3_len);

-        tmp += str3_len;

-    }

-    if (str4) {

-        memcpy(tmp, str4, str1_len);

-    }

-    return str;

-}

-

-char *str_dup(const char *str)

-{

-    return str_cat4(str, NULL, NULL, NULL);

-}

-

-bool str_eq(const char *str1, const char *str2)

-{

-    if (!str1 && !str2)

-        return true;

-    if (!str1 || !str2)

-        return false;

-    if (0 == strcmp(str1, str2))

-        return true;

-    return false;

-}

-

-bool str_ieq(const char *str1, const char *str2)

-{

-    if (!str1 && !str2)

-        return true;

-    if (!str1 || !str2)

-        return false;

-    if (0 == strcasecmp(str1, str2))

-        return true;

-    return false;

-}

-

-bool str_endswith(const char *txt, const char *end)

-{

-    size_t end_len;

-    size_t txt_len;

-

-    if (!txt || !end)

-        return false;

-

-    txt_len = strlen(txt);

-    end_len = strlen(end);

-    if (end_len > txt_len)

-        return false;

-    if (str_eq(txt+txt_len-end_len, end))

-        return true;

-    return false;

-}

-

-/* TODO: probably should move to some other file and change name to

-   sleep_milliseconds */

-void sleep_milliseconds(int milliseconds)

-{

-#ifdef _WIN32

-    Sleep((DWORD)milliseconds);

-#else

-    struct timespec tv;

-    int             secs, nanosecs;

-    secs = milliseconds / 1000;

-    nanosecs = (milliseconds - (secs * 1000)) * 1000;

-    tv.tv_sec = (time_t) secs;

-    tv.tv_nsec = (long) nanosecs;

-    while (1)

-    {

-        int rval = nanosleep(&tv, &tv);

-        if (rval == 0)

-            /* Completed the entire sleep time; all done. */

-            return;

-        else if (errno == EINTR)

-            /* Interrupted by a signal. Try again. */

-            continue;

-        else

-            /* Some other error; bail out. */

-            return;

-    }

-    return;

-#endif

-}

-

-#ifndef HAVE_STRCPY_S

-void strcpy_s(char* dst, size_t dst_size, const char* src)

-{

-    size_t src_size = strlen(src) + 1;

-    if (src_size <= dst_size)

-        memcpy(dst, src, src_size);

-    else {

-        if (dst_size > 0) {

-            memcpy(dst, src, dst_size);

-            dst[dst_size-1] = 0;

-        }

-    }

-}

-#endif

-

-#ifndef HAVE_STRCAT_S

-void strcat_s(char *dst, size_t dst_size, const char* src)

-{

-    size_t dst_len = strlen(dst);

-    if (dst_len >= dst_size) {

-        if (dst_size > 0)

-            dst[dst_size-1] = 0;

-        return;

-    }

-    strcpy_s(dst+dst_len, dst_size - dst_len, src);

-}

-#endif

-

-static SplashColorMode gSplashColorMode = splashModeBGR8;

-

-static SplashColor splashColRed;

-static SplashColor splashColGreen;

-static SplashColor splashColBlue;

-static SplashColor splashColWhite;

-static SplashColor splashColBlack;

-

-#define SPLASH_COL_RED_PTR (SplashColorPtr)&(splashColRed[0])

-#define SPLASH_COL_GREEN_PTR (SplashColorPtr)&(splashColGreen[0])

-#define SPLASH_COL_BLUE_PTR (SplashColorPtr)&(splashColBlue[0])

-#define SPLASH_COL_WHITE_PTR (SplashColorPtr)&(splashColWhite[0])

-#define SPLASH_COL_BLACK_PTR (SplashColorPtr)&(splashColBlack[0])

-

-static SplashColorPtr  gBgColor = SPLASH_COL_WHITE_PTR;

-

-static void splashColorSet(SplashColorPtr col, Guchar red, Guchar green, Guchar blue, Guchar alpha)

-{

-    switch (gSplashColorMode)

-    {

-        case splashModeBGR8:

-            col[0] = blue;

-            col[1] = green;

-            col[2] = red;

-            break;

-        case splashModeRGB8:

-            col[0] = red;

-            col[1] = green;

-            col[2] = blue;

-            break;

-        default:

-            assert(0);

-            break;

-    }

-}

-

-void SplashColorsInit(void)

-{

-    splashColorSet(SPLASH_COL_RED_PTR, 0xff, 0, 0, 0);

-    splashColorSet(SPLASH_COL_GREEN_PTR, 0, 0xff, 0, 0);

-    splashColorSet(SPLASH_COL_BLUE_PTR, 0, 0, 0xff, 0);

-    splashColorSet(SPLASH_COL_BLACK_PTR, 0, 0, 0, 0);

-    splashColorSet(SPLASH_COL_WHITE_PTR, 0xff, 0xff, 0xff, 0);

-}

-

-PdfEnginePoppler::PdfEnginePoppler() : 

-   _fileName(0)

-   , _pageCount(INVALID_PAGE_NO) 

-   , _pdfDoc(NULL)

-   , _outputDev(NULL)

-{

-}

-

-PdfEnginePoppler::~PdfEnginePoppler()

-{

-    free(_fileName);

-    delete _outputDev;

-    delete _pdfDoc;

-}

-

-bool PdfEnginePoppler::load(const char *fileName)

-{

-    setFileName(fileName);

-    /* note: don't delete fileNameStr since PDFDoc takes ownership and deletes them itself */

-    GooString *fileNameStr = new GooString(fileName);

-    if (!fileNameStr) return false;

-

-    _pdfDoc = new PDFDoc(fileNameStr, NULL, NULL, (void*)NULL);

-    if (!_pdfDoc->isOk()) {

-        return false;

-    }

-    _pageCount = _pdfDoc->getNumPages();

-    return true;

-}

-

-SplashOutputDev * PdfEnginePoppler::outputDevice() {

-    if (!_outputDev) {

-        GBool bitmapTopDown = gTrue;

-        _outputDev = new SplashOutputDev(gSplashColorMode, 4, gFalse, gBgColor, bitmapTopDown);

-        if (_outputDev)

-            _outputDev->startDoc(_pdfDoc);

-    }

-    return _outputDev;

-}

-

-SplashBitmap *PdfEnginePoppler::renderBitmap(int pageNo, double zoomReal, int rotation)

-{

-    assert(outputDevice());

-    if (!outputDevice()) return NULL;

-

-    double hDPI = (double)PDF_FILE_DPI * zoomReal * 0.01;

-    double vDPI = (double)PDF_FILE_DPI * zoomReal * 0.01;

-    GBool  useMediaBox = gFalse;

-    GBool  crop        = gTrue;

-    GBool  doLinks     = gTrue;

-    _pdfDoc->displayPage(_outputDev, pageNo, hDPI, vDPI, rotation, useMediaBox, 

-        crop, doLinks, NULL, NULL);

-

-    SplashBitmap* bmp = _outputDev->takeBitmap();

-    return bmp;

-}

-

-struct FindFileState {

-    char path[MAX_FILENAME_SIZE];

-    char dirpath[MAX_FILENAME_SIZE]; /* current dir path */

-    char pattern[MAX_FILENAME_SIZE]; /* search pattern */

-    const char *bufptr;

-#ifdef _WIN32

-    WIN32_FIND_DATA fileinfo;

-    HANDLE dir;

-#else

-    DIR *dir;

-#endif

-};

-

-#ifdef _WIN32

-#include <sys/timeb.h>

-#include <direct.h>

-

-__inline char *getcwd(char *buffer, int maxlen)

-{

-    return _getcwd(buffer, maxlen);

-}

-

-int fnmatch(const char *pattern, const char *string, int flags)

-{

-    int prefix_len;

-    const char *star_pos = strchr(pattern, '*');

-    if (!star_pos)

-        return strcmp(pattern, string) != 0;

-

-    prefix_len = (int)(star_pos-pattern);

-    if (0 == prefix_len)

-        return 0;

-

-    if (0 == _strnicmp(pattern, string, prefix_len))

-        return 0;

-

-    return 1;

-}

-

-#else

-#include <fnmatch.h>

-#endif

-

-#ifdef _WIN32

-/* on windows to query dirs we need foo\* to get files in this directory.

-    foo\ always fails and foo will return just info about foo directory,

-    not files in this directory */

-static void win_correct_path_for_FindFirstFile(char *path, int path_max_len)

-{

-    int path_len = strlen(path);

-    if (path_len >= path_max_len-4)

-        return;

-    if (DIR_SEP_CHAR != path[path_len])

-        path[path_len++] = DIR_SEP_CHAR;

-    path[path_len++] = '*';

-    path[path_len] = 0;

-}

-#endif

-

-FindFileState *find_file_open(const char *path, const char *pattern)

-{

-    FindFileState *s;

-

-    s = (FindFileState*)malloc(sizeof(FindFileState));

-    if (!s)

-        return NULL;

-    strcpy_s(s->path, sizeof(s->path), path);

-    strcpy_s(s->dirpath, sizeof(s->path), path);

-#ifdef _WIN32

-    win_correct_path_for_FindFirstFile(s->path, sizeof(s->path));

-#endif

-    strcpy_s(s->pattern, sizeof(s->pattern), pattern);

-    s->bufptr = s->path;

-#ifdef _WIN32

-    s->dir = INVALID_HANDLE_VALUE;

-#else

-    s->dir = NULL;

-#endif

-    return s;

-}

-

-#if 0 /* re-enable if we #define USE_OWN_GET_AUTH_DATA */

-void *StandardSecurityHandler::getAuthData()

-{

-    return NULL;

-}

-#endif

-

-char *makepath(char *buf, int buf_size, const char *path,

-               const char *filename)

-{

-    strcpy_s(buf, buf_size, path);

-    int len = strlen(path);

-    if (len > 0 && path[len - 1] != DIR_SEP_CHAR && len + 1 < buf_size) {

-        buf[len++] = DIR_SEP_CHAR;

-        buf[len] = '\0';

-    }

-    strcat_s(buf, buf_size, filename);

-    return buf;

-}

-

-#ifdef _WIN32

-static int skip_matching_file(const char *filename)

-{

-    if (0 == strcmp(".", filename))

-        return 1;

-    if (0 == strcmp("..", filename))

-        return 1;

-    return 0;

-}

-#endif

-

-int find_file_next(FindFileState *s, char *filename, int filename_size_max)

-{

-#ifdef _WIN32

-    int    fFound;

-    if (INVALID_HANDLE_VALUE == s->dir) {

-        s->dir = FindFirstFile(s->path, &(s->fileinfo));

-        if (INVALID_HANDLE_VALUE == s->dir)

-            return -1;

-        goto CheckFile;

-    }

-

-    while (1) {

-        fFound = FindNextFile(s->dir, &(s->fileinfo));

-        if (!fFound)

-            return -1;

-CheckFile:

-        if (skip_matching_file(s->fileinfo.cFileName))

-            continue;

-        if (0 == fnmatch(s->pattern, s->fileinfo.cFileName, 0) ) {

-            makepath(filename, filename_size_max, s->dirpath, s->fileinfo.cFileName);

-            return 0;

-        }

-    }

-#else

-    struct dirent *dirent;

-    const char *p;

-    char *q;

-

-    if (s->dir == NULL)

-        goto redo;

-

-    for (;;) {

-        dirent = readdir(s->dir);

-        if (dirent == NULL) {

-        redo:

-            if (s->dir) {

-                closedir(s->dir);

-                s->dir = NULL;

-            }

-            p = s->bufptr;

-            if (*p == '\0')

-                return -1;

-            /* CG: get_str(&p, s->dirpath, sizeof(s->dirpath), ":") */

-            q = s->dirpath;

-            while (*p != ':' && *p != '\0') {

-                if ((q - s->dirpath) < (int)sizeof(s->dirpath) - 1)

-                    *q++ = *p;

-                p++;

-            }

-            *q = '\0';

-            if (*p == ':')

-                p++;

-            s->bufptr = p;

-            s->dir = opendir(s->dirpath);

-            if (!s->dir)

-                goto redo;

-        } else {

-            if (fnmatch(s->pattern, dirent->d_name, 0) == 0) {

-                makepath(filename, filename_size_max,

-                         s->dirpath, dirent->d_name);

-                return 0;

-            }

-        }

-    }

-#endif

-}

-

-void find_file_close(FindFileState *s)

-{

-#ifdef _WIN32

-    if (INVALID_HANDLE_VALUE != s->dir)

-       FindClose(s->dir);

-#else

-    if (s->dir)

-        closedir(s->dir);

-#endif

-    free(s);

-}

-

-int StrList_Len(StrList **root)

-{

-    int         len = 0;

-    StrList *   cur;

-    assert(root);

-    if (!root)

-        return 0;

-    cur = *root;

-    while (cur) {

-        ++len;

-        cur = cur->next;

-    }

-    return len;

-}

-

-int StrList_InsertAndOwn(StrList **root, char *txt)

-{

-    StrList *   el;

-    assert(root && txt);

-    if (!root || !txt)

-        return false;

-

-    el = (StrList*)malloc(sizeof(StrList));

-    if (!el)

-        return false;

-    el->str = txt;

-    el->next = *root;

-    *root = el;

-    return true;

-}

-

-int StrList_Insert(StrList **root, char *txt)

-{

-    char *txtDup;

-

-    assert(root && txt);

-    if (!root || !txt)

-        return false;

-    txtDup = str_dup(txt);

-    if (!txtDup)

-        return false;

-

-    if (!StrList_InsertAndOwn(root, txtDup)) {

-        free((void*)txtDup);

-        return false;

-    }

-    return true;

-}

-

-StrList* StrList_RemoveHead(StrList **root)

-{

-    StrList *tmp;

-    assert(root);

-    if (!root)

-        return NULL;

-

-    if (!*root)

-        return NULL;

-    tmp = *root;

-    *root = tmp->next;

-    tmp->next = NULL;

-    return tmp;

-}

-

-void StrList_FreeElement(StrList *el)

-{

-    if (!el)

-        return;

-    free((void*)el->str);

-    free((void*)el);

-}

-

-void StrList_Destroy(StrList **root)

-{

-    StrList *   cur;

-    StrList *   next;

-

-    if (!root)

-        return;

-    cur = *root;

-    while (cur) {

-        next = cur->next;

-        StrList_FreeElement(cur);

-        cur = next;

-    }

-    *root = NULL;

-}

-

-#ifndef _WIN32

-void OutputDebugString(const char *txt)

-{

-    /* do nothing */

-}

-#define _snprintf snprintf

-#define _vsnprintf vsnprintf

-#endif

-

-void my_error(void *, ErrorCategory, Goffset pos, char *msg) {

-#if 0

-    char        buf[4096], *p = buf;

-

-    // NB: this can be called before the globalParams object is created

-    if (globalParams && globalParams->getErrQuiet()) {

-        return;

-    }

-

-    if (pos >= 0) {

-      p += _snprintf(p, sizeof(buf)-1, "Error (%lld): ", (long long)pos);

-        *p   = '\0';

-        OutputDebugString(p);

-    } else {

-        OutputDebugString("Error: ");

-    }

-

-    p = buf;

-    p += _vsnprintf(p, sizeof(buf) - 1, msg, args);

-    while ( p > buf  &&  isspace(p[-1]) )

-            *--p = '\0';

-    *p++ = '\r';

-    *p++ = '\n';

-    *p   = '\0';

-    OutputDebugString(buf);

-

-    if (pos >= 0) {

-        p += _snprintf(p, sizeof(buf)-1, "Error (%lld): ", (long long)pos);

-        *p   = '\0';

-        OutputDebugString(buf);

-        if (gErrFile)

-            fprintf(gErrFile, buf);

-    } else {

-        OutputDebugString("Error: ");

-        if (gErrFile)

-            fprintf(gErrFile, "Error: ");

-    }

-#endif

-#if 0

-    p = buf;

-    va_start(args, msg);

-    p += _vsnprintf(p, sizeof(buf) - 3, msg, args);

-    while ( p > buf  &&  isspace(p[-1]) )

-            *--p = '\0';

-    *p++ = '\r';

-    *p++ = '\n';

-    *p   = '\0';

-    OutputDebugString(buf);

-    if (gErrFile)

-        fprintf(gErrFile, buf);

-    va_end(args);

-#endif

-}

-

-void LogInfo(const char *fmt, ...)

-{

-    va_list args;

-    char        buf[4096], *p = buf;

-

-    p = buf;

-    va_start(args, fmt);

-    p += _vsnprintf(p, sizeof(buf) - 1, fmt, args);

-    *p   = '\0';

-    fprintf(gOutFile, "%s", buf);

-    va_end(args);

-    fflush(gOutFile);

-}

-

-static void PrintUsageAndExit(int argc, char **argv)

-{

-    printf("Usage: pdftest [-preview|-slowpreview] [-loadonly] [-timings] [-text] [-resolution NxM] [-recursive] [-page N] [-out out.txt] pdf-files-to-process\n");

-    for (int i=0; i < argc; i++) {

-        printf("i=%d, '%s'\n", i, argv[i]);

-    }

-    exit(0);

-}

-

-static bool ShowPreview(void)

-{

-    if (gfPreview || gfSlowPreview)

-        return true;

-    return false;

-}

-

-static void RenderPdfAsText(const char *fileName)

-{

-    GooString *         fileNameStr = NULL;

-    PDFDoc *            pdfDoc = NULL;

-    GooString *         txt = NULL;

-    int                 pageCount;

-    double              timeInMs;

-

-    assert(fileName);

-    if (!fileName)

-        return;

-

-    LogInfo("started: %s\n", fileName);

-

-    TextOutputDev * textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse);

-    if (!textOut->isOk()) {

-        delete textOut;

-        return;

-    }

-

-    GooTimer msTimer;

-    /* note: don't delete fileNameStr since PDFDoc takes ownership and deletes them itself */

-    fileNameStr = new GooString(fileName);

-    if (!fileNameStr)

-        goto Exit;

-

-    pdfDoc = new PDFDoc(fileNameStr, NULL, NULL, NULL);

-    if (!pdfDoc->isOk()) {

-        error(errIO, -1, "RenderPdfFile(): failed to open PDF file {0:s}\n", fileName);

-        goto Exit;

-    }

-

-    msTimer.stop();

-    timeInMs = msTimer.getElapsed();

-    LogInfo("load: %.2f ms\n", timeInMs);

-

-    pageCount = pdfDoc->getNumPages();

-    LogInfo("page count: %d\n", pageCount);

-

-    for (int curPage = 1; curPage <= pageCount; curPage++) {

-        if ((gPageNo != PAGE_NO_NOT_GIVEN) && (gPageNo != curPage))

-            continue;

-

-        msTimer.start();

-        int rotate = 0;

-        GBool useMediaBox = gFalse;

-        GBool crop = gTrue;

-        GBool doLinks = gFalse;

-        pdfDoc->displayPage(textOut, curPage, 72, 72, rotate, useMediaBox, crop, doLinks);

-        txt = textOut->getText(0.0, 0.0, 10000.0, 10000.0);

-        msTimer.stop();

-        timeInMs = msTimer.getElapsed();

-        if (gfTimings)

-            LogInfo("page %d: %.2f ms\n", curPage, timeInMs);

-        printf("%s\n", txt->getCString());

-        delete txt;

-        txt = NULL;

-    }

-

-Exit:

-    LogInfo("finished: %s\n", fileName);

-    delete textOut;

-    delete pdfDoc;

-}

-

-#ifdef _MSC_VER

-#define POPPLER_TMP_NAME "c:\\poppler_tmp.pdf"

-#else

-#define POPPLER_TMP_NAME "/tmp/poppler_tmp.pdf"

-#endif

-

-static void RenderPdf(const char *fileName)

-{

-    const char *        fileNameSplash = NULL;

-    PdfEnginePoppler *  engineSplash = NULL;

-    int                 pageCount;

-    double              timeInMs;

-

-#ifdef COPY_FILE

-    // TODO: fails if file already exists and has read-only attribute

-    CopyFile(fileName, POPPLER_TMP_NAME, false);

-    fileNameSplash = POPPLER_TMP_NAME;

-#else

-    fileNameSplash = fileName;

-#endif

-    LogInfo("started: %s\n", fileName);

-

-    engineSplash = new PdfEnginePoppler();

-

-    GooTimer msTimer;

-    if (!engineSplash->load(fileNameSplash)) {

-        LogInfo("failed to load splash\n");

-        goto Error;

-    }

-    msTimer.stop();

-    timeInMs = msTimer.getElapsed();

-    LogInfo("load splash: %.2f ms\n", timeInMs);

-    pageCount = engineSplash->pageCount();

-

-    LogInfo("page count: %d\n", pageCount);

-    if (gfLoadOnly)

-        goto Error;

-

-    for (int curPage = 1; curPage <= pageCount; curPage++) {

-        if ((gPageNo != PAGE_NO_NOT_GIVEN) && (gPageNo != curPage))

-            continue;

-

-        SplashBitmap *bmpSplash = NULL;

-

-        GooTimer msTimer;

-        bmpSplash = engineSplash->renderBitmap(curPage, 100.0, 0);

-        msTimer.stop();

-        double timeInMs = msTimer.getElapsed();

-        if (gfTimings) {

-            if (!bmpSplash)

-                LogInfo("page splash %d: failed to render\n", curPage);

-            else

-                LogInfo("page splash %d (%dx%d): %.2f ms\n", curPage, bmpSplash->getWidth(), bmpSplash->getHeight(), timeInMs);

-        }

-

-        if (ShowPreview()) {

-            PreviewBitmapSplash(bmpSplash);

-            if (gfSlowPreview)

-                sleep_milliseconds(SLOW_PREVIEW_TIME);

-        }

-        delete bmpSplash;

-    }

-Error:

-    delete engineSplash;

-    LogInfo("finished: %s\n", fileName);

-}

-

-static void RenderFile(const char *fileName)

-{

-    if (gfTextOnly) {

-        RenderPdfAsText(fileName);

-        return;

-    }

-

-    RenderPdf(fileName);

-}

-

-static bool ParseInteger(const char *start, const char *end, int *intOut)

-{

-    char            numBuf[16];

-    int             digitsCount;

-    const char *    tmp;

-

-    assert(start && end && intOut);

-    assert(end >= start);

-    if (!start || !end || !intOut || (start > end))

-        return false;

-

-    digitsCount = 0;

-    tmp = start;

-    while (tmp <= end) {

-        if (isspace(*tmp)) {

-            /* do nothing, we allow whitespace */

-        } else if (!isdigit(*tmp))

-            return false;

-        numBuf[digitsCount] = *tmp;

-        ++digitsCount;

-        if (digitsCount == dimof(numBuf)-3) /* -3 to be safe */

-            return false;

-        ++tmp;

-    }

-    if (0 == digitsCount)

-        return false;

-    numBuf[digitsCount] = 0;

-    *intOut = atoi(numBuf);

-    return true;

-}

-

-/* Given 'resolutionString' in format NxM (e.g. "100x200"), parse the string and put N

-   into 'resolutionXOut' and M into 'resolutionYOut'.

-   Return false if there was an error (e.g. string is not in the right format */

-static bool ParseResolutionString(const char *resolutionString, int *resolutionXOut, int *resolutionYOut)

-{

-    const char *    posOfX;

-

-    assert(resolutionString);

-    assert(resolutionXOut);

-    assert(resolutionYOut);

-    if (!resolutionString || !resolutionXOut || !resolutionYOut)

-        return false;

-    *resolutionXOut = 0;

-    *resolutionYOut = 0;

-    posOfX = strchr(resolutionString, 'X');

-    if (!posOfX)

-        posOfX = strchr(resolutionString, 'x');

-    if (!posOfX)

-        return false;

-    if (posOfX == resolutionString)

-        return false;

-    if (!ParseInteger(resolutionString, posOfX-1, resolutionXOut))

-        return false;

-    if (!ParseInteger(posOfX+1, resolutionString+strlen(resolutionString)-1, resolutionYOut))

-        return false;

-    return true;

-}

-

-static void ParseCommandLine(int argc, char **argv)

-{

-    char *      arg;

-

-    if (argc < 2)

-        PrintUsageAndExit(argc, argv);

-

-    for (int i=1; i < argc; i++) {

-        arg = argv[i];

-        assert(arg);

-        if ('-' == arg[0]) {

-            if (str_ieq(arg, TIMINGS_ARG)) {

-                gfTimings = true;

-            } else if (str_ieq(arg, RESOLUTION_ARG)) {

-                ++i;

-                if (i == argc)

-                    PrintUsageAndExit(argc, argv); /* expect a file name after that */

-                if (!ParseResolutionString(argv[i], &gResolutionX, &gResolutionY))

-                    PrintUsageAndExit(argc, argv);

-                gfForceResolution = true;

-            } else if (str_ieq(arg, RECURSIVE_ARG)) {

-                gfRecursive = true;

-            } else if (str_ieq(arg, OUT_ARG)) {

-                /* expect a file name after that */

-                ++i;

-                if (i == argc)

-                    PrintUsageAndExit(argc, argv);

-                gOutFileName = str_dup(argv[i]);

-            } else if (str_ieq(arg, PREVIEW_ARG)) {

-                gfPreview = true;

-            } else if (str_ieq(arg, TEXT_ARG)) {

-                gfTextOnly = true;

-            } else if (str_ieq(arg, SLOW_PREVIEW_ARG)) {

-                gfSlowPreview = true;

-            } else if (str_ieq(arg, LOAD_ONLY_ARG)) {

-                gfLoadOnly = true;

-            } else if (str_ieq(arg, PAGE_ARG)) {

-                /* expect an integer after that */

-                ++i;

-                if (i == argc)

-                    PrintUsageAndExit(argc, argv);

-                gPageNo = atoi(argv[i]);

-                if (gPageNo < 1)

-                    PrintUsageAndExit(argc, argv);

-            } else {

-                /* unknown option */

-                PrintUsageAndExit(argc, argv);

-            }

-        } else {

-            /* we assume that this is not an option hence it must be

-               a name of PDF/directory/file with PDF names */

-            StrList_Insert(&gArgsListRoot, arg);

-        }

-    }

-}

-

-#if 0

-void RenderFileList(char *pdfFileList)

-{

-    char *data = NULL;

-    char *dataNormalized = NULL;

-    char *pdfFileName;

-    uint64_t fileSize;

-

-    assert(pdfFileList);

-    if (!pdfFileList)

-        return;

-    data = file_read_all(pdfFileList, &fileSize);

-    if (!data) {

-        error(-1, "couldn't load file '%s'", pdfFileList);

-        return;

-    }

-    dataNormalized = str_normalize_newline(data, UNIX_NEWLINE);

-    if (!dataNormalized) {

-        error(-1, "couldn't normalize data of file '%s'", pdfFileList);

-        goto Exit;

-    }

-    for (;;) {

-        pdfFileName = str_split_iter(&dataNormalized, UNIX_NEWLINE_C);

-        if (!pdfFileName)

-            break;

-        str_strip_ws_both(pdfFileName);

-        if (str_empty(pdfFileName)) {

-            free((void*)pdfFileName);

-            continue;

-        }

-        RenderFile(pdfFileName);

-        free((void*)pdfFileName);

-    }

-Exit:

-    free((void*)dataNormalized);

-    free((void*)data);

-}

-#endif

-

-#ifdef _WIN32

-#include <sys/types.h>

-#include <sys/stat.h>

-

-bool IsDirectoryName(char *path)

-{

-    struct _stat    buf;

-    int             result;

-

-    result = _stat(path, &buf );

-    if (0 != result)

-        return false;

-

-    if (buf.st_mode & _S_IFDIR)

-        return true;

-

-    return false;

-}

-

-bool IsFileName(char *path)

-{

-    struct _stat    buf;

-    int             result;

-

-    result = _stat(path, &buf );

-    if (0 != result)

-        return false;

-

-    if (buf.st_mode & _S_IFREG)

-        return true;

-

-    return false;

-}

-#else

-bool IsDirectoryName(char *path)

-{

-    /* TODO: implement me */

-    return false;

-}

-

-bool IsFileName(char *path)

-{

-    /* TODO: implement me */

-    return true;

-}

-#endif

-

-bool IsPdfFileName(char *path)

-{

-    if (str_endswith(path, ".pdf"))

-        return true;

-    return false;

-}

-

-static void RenderDirectory(char *path)

-{

-    FindFileState * ffs;

-    char            filename[MAX_FILENAME_SIZE];

-    StrList *       dirList = NULL;

-    StrList *       el;

-

-    StrList_Insert(&dirList, path);

-

-    while (0 != StrList_Len(&dirList)) {

-        el = StrList_RemoveHead(&dirList);

-        ffs = find_file_open(el->str, "*");

-        while (!find_file_next(ffs, filename, sizeof(filename))) {

-            if (IsDirectoryName(filename)) {

-                if (gfRecursive) {

-                    StrList_Insert(&dirList, filename);

-                }

-            } else if (IsFileName(filename)) {

-                if (IsPdfFileName(filename)) {

-                    RenderFile(filename);

-                }

-            }

-        }

-        find_file_close(ffs);

-        StrList_FreeElement(el);

-    }

-    StrList_Destroy(&dirList);

-}

-

-/* Render 'cmdLineArg', which can be:

-   - directory name

-   - name of PDF file

-   - name of text file with names of PDF files

-*/

-static void RenderCmdLineArg(char *cmdLineArg)

-{

-    assert(cmdLineArg);

-    if (!cmdLineArg)

-        return;

-    if (IsDirectoryName(cmdLineArg)) {

-        RenderDirectory(cmdLineArg);

-    } else if (IsFileName(cmdLineArg)) {

-        if (IsPdfFileName(cmdLineArg))

-            RenderFile(cmdLineArg);

-#if 0

-        else

-            RenderFileList(cmdLineArg);

-#endif

-    } else {

-        error(errCommandLine, -1, "unexpected argument '{0:s}'", cmdLineArg);

-    }

-}

-

-int main(int argc, char **argv)

-{

-    setErrorCallback(my_error, NULL);

-    ParseCommandLine(argc, argv);

-    if (0 == StrList_Len(&gArgsListRoot))

-        PrintUsageAndExit(argc, argv);

-    assert(gArgsListRoot);

-

-    SplashColorsInit();

-    globalParams = new GlobalParams();

-    if (!globalParams)

-        return 1;

-    globalParams->setErrQuiet(gFalse);

-

-    FILE * outFile = NULL;

-    if (gOutFileName) {

-        outFile = fopen(gOutFileName, "wb");

-        if (!outFile) {

-            printf("failed to open -out file %s\n", gOutFileName);

-            return 1;

-        }

-        gOutFile = outFile;

-    }

-    else

-        gOutFile = stdout;

-

-    if (gOutFileName)

-        gErrFile = outFile;

-    else

-        gErrFile = stderr;

-

-    PreviewBitmapInit();

-

-    StrList * curr = gArgsListRoot;

-    while (curr) {

-        RenderCmdLineArg(curr->str);

-        curr = curr->next;

-    }

-    if (outFile)

-        fclose(outFile);

-    PreviewBitmapDestroy();

-    StrList_Destroy(&gArgsListRoot);

-    delete globalParams;

-    free(gOutFileName);

-    return 0;

-}

-

+/* Copyright Krzysztof Kowalczyk 2006-2007
+   Copyright Hib Eris <hib@hiberis.nl> 2008, 2013
+   License: GPLv2 */
+/*
+  A tool to stress-test poppler rendering and measure rendering times for
+  very simplistic performance measuring.
+
+  TODO:
+   * make it work with cairo output as well
+   * print more info about document like e.g. enumarate images,
+     streams, compression, encryption, password-protection. Each should have
+     a command-line arguments to turn it on/off
+   * never over-write file given as -out argument (optionally, provide -force
+     option to force writing the -out file). It's way too easy too lose results
+     of a previous run.
+*/
+
+#ifdef _MSC_VER
+// this sucks but I don't know any other way
+#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
+#endif
+
+#include <config.h>
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <strings.h>
+#endif
+
+// Define COPY_FILE if you want the file to be copied to a local disk first
+// before it's tested. This is desired if a file is on a slow drive.
+// Currently copying only works on Windows.
+// Not enabled by default.
+//#define COPY_FILE 1
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+
+#include "Error.h"
+#include "ErrorCodes.h"
+#include "goo/GooString.h"
+#include "goo/GooList.h"
+#include "goo/GooTimer.h"
+#include "GlobalParams.h"
+#include "splash/SplashBitmap.h"
+#include "Object.h" /* must be included before SplashOutputDev.h because of sloppiness in SplashOutputDev.h */
+#include "SplashOutputDev.h"
+#include "TextOutputDev.h"
+#include "PDFDoc.h"
+#include "Link.h"
+
+#ifdef _MSC_VER
+#define strdup _strdup
+#define strcasecmp _stricmp
+#endif
+
+#define dimof(X)    (sizeof(X)/sizeof((X)[0]))
+
+#define INVALID_PAGE_NO     -1
+
+/* Those must be implemented in order to provide preview during execution.
+   They can be no-ops. An implementation for windows is in
+   perf-test-preview-win.cc
+*/
+extern void PreviewBitmapInit(void);
+extern void PreviewBitmapDestroy(void);
+extern void PreviewBitmapSplash(SplashBitmap *bmpSplash);
+
+class PdfEnginePoppler {
+public:
+    PdfEnginePoppler();
+    ~PdfEnginePoppler();
+
+    const char *fileName(void) const { return _fileName; };
+
+    void setFileName(const char *fileName) {
+        assert(!_fileName);
+        _fileName = (char*)strdup(fileName);
+    }
+
+    int pageCount(void) const { return _pageCount; }
+
+    bool load(const char *fileName);
+    SplashBitmap *renderBitmap(int pageNo, double zoomReal, int rotation);
+
+    SplashOutputDev *   outputDevice();
+private:
+    char *              _fileName;
+    int                 _pageCount;
+
+    PDFDoc *            _pdfDoc;
+    SplashOutputDev *   _outputDev;
+};
+
+typedef struct StrList {
+    struct StrList *next;
+    char *          str;
+} StrList;
+
+/* List of all command-line arguments that are not switches.
+   We assume those are:
+     - names of PDF files
+     - names of a file with a list of PDF files
+     - names of directories with PDF files
+*/
+static StrList *gArgsListRoot = NULL;
+
+/* Names of all command-line switches we recognize */
+#define TIMINGS_ARG         "-timings"
+#define RESOLUTION_ARG      "-resolution"
+#define RECURSIVE_ARG       "-recursive"
+#define OUT_ARG             "-out"
+#define PREVIEW_ARG         "-preview"
+#define SLOW_PREVIEW_ARG    "-slowpreview"
+#define LOAD_ONLY_ARG       "-loadonly"
+#define PAGE_ARG            "-page"
+#define TEXT_ARG            "-text"
+
+/* Should we record timings? True if -timings command-line argument was given. */
+static bool gfTimings = false;
+
+/* If true, we use render each page at resolution 'gResolutionX'/'gResolutionY'.
+   If false, we render each page at its native resolution.
+   True if -resolution NxM command-line argument was given. */
+static bool gfForceResolution = false;
+static int  gResolutionX = 0;
+static int  gResolutionY = 0;
+/* If NULL, we output the log info to stdout. If not NULL, should be a name
+   of the file to which we output log info.
+   Controled by -out command-line argument. */
+static char *   gOutFileName = NULL;
+/* FILE * correspondig to gOutFileName or stdout if gOutFileName is NULL or
+   was invalid name */
+static FILE *   gOutFile = NULL;
+/* FILE * correspondig to gOutFileName or stderr if gOutFileName is NULL or
+   was invalid name */
+static FILE *   gErrFile = NULL;
+
+/* If True and a directory is given as a command-line argument, we'll process
+   pdf files in sub-directories as well.
+   Controlled by -recursive command-line argument */
+static bool gfRecursive = false;
+
+/* If true, preview rendered image. To make sure that they're being rendered correctly. */
+static bool gfPreview = false;
+
+/* 1 second (1000 milliseconds) */
+#define SLOW_PREVIEW_TIME 1000
+
+/* If true, preview rendered image in a slow mode i.e. delay displaying for
+   SLOW_PREVIEW_TIME. This is so that a human has enough time to see if the
+   PDF renders ok. In release mode on fast processor pages take only ~100-200 ms
+   to render and they go away too quickly to be inspected by a human. */
+static bool gfSlowPreview = false;
+
+/* If true, we only dump the text, not render */
+static bool gfTextOnly = false;
+
+#define PAGE_NO_NOT_GIVEN -1
+
+/* If equals PAGE_NO_NOT_GIVEN, we're in default mode where we render all pages.
+   If different, will only render this page */
+static int  gPageNo = PAGE_NO_NOT_GIVEN;
+/* If true, will only load the file, not render any pages. Mostly for
+   profiling load time */
+static bool gfLoadOnly = false;
+
+#define PDF_FILE_DPI 72
+
+#define MAX_FILENAME_SIZE 1024
+
+/* DOS is 0xd 0xa */
+#define DOS_NEWLINE "\x0d\x0a"
+/* Mac is single 0xd */
+#define MAC_NEWLINE "\x0d"
+/* Unix is single 0xa (10) */
+#define UNIX_NEWLINE "\x0a"
+#define UNIX_NEWLINE_C 0xa
+
+#ifdef _WIN32
+  #define DIR_SEP_CHAR '\\'
+  #define DIR_SEP_STR  "\\"
+#else
+  #define DIR_SEP_CHAR '/'
+  #define DIR_SEP_STR  "/"
+#endif
+
+void memzero(void *data, size_t len)
+{
+    memset(data, 0, len);
+}
+
+void *zmalloc(size_t len)
+{
+    void *data = malloc(len);
+    if (data)
+        memzero(data, len);
+    return data;
+}
+
+/* Concatenate 4 strings. Any string can be NULL.
+   Caller needs to free() memory. */
+char *str_cat4(const char *str1, const char *str2, const char *str3, const char *str4)
+{
+    char *str;
+    char *tmp;
+    size_t str1_len = 0;
+    size_t str2_len = 0;
+    size_t str3_len = 0;
+    size_t str4_len = 0;
+
+    if (str1)
+        str1_len = strlen(str1);
+    if (str2)
+        str2_len = strlen(str2);
+    if (str3)
+        str3_len = strlen(str3);
+    if (str4)
+        str4_len = strlen(str4);
+
+    str = (char*)zmalloc(str1_len + str2_len + str3_len + str4_len + 1);
+    if (!str)
+        return NULL;
+
+    tmp = str;
+    if (str1) {
+        memcpy(tmp, str1, str1_len);
+        tmp += str1_len;
+    }
+    if (str2) {
+        memcpy(tmp, str2, str2_len);
+        tmp += str2_len;
+    }
+    if (str3) {
+        memcpy(tmp, str3, str3_len);
+        tmp += str3_len;
+    }
+    if (str4) {
+        memcpy(tmp, str4, str1_len);
+    }
+    return str;
+}
+
+char *str_dup(const char *str)
+{
+    return str_cat4(str, NULL, NULL, NULL);
+}
+
+bool str_eq(const char *str1, const char *str2)
+{
+    if (!str1 && !str2)
+        return true;
+    if (!str1 || !str2)
+        return false;
+    if (0 == strcmp(str1, str2))
+        return true;
+    return false;
+}
+
+bool str_ieq(const char *str1, const char *str2)
+{
+    if (!str1 && !str2)
+        return true;
+    if (!str1 || !str2)
+        return false;
+    if (0 == strcasecmp(str1, str2))
+        return true;
+    return false;
+}
+
+bool str_endswith(const char *txt, const char *end)
+{
+    size_t end_len;
+    size_t txt_len;
+
+    if (!txt || !end)
+        return false;
+
+    txt_len = strlen(txt);
+    end_len = strlen(end);
+    if (end_len > txt_len)
+        return false;
+    if (str_eq(txt+txt_len-end_len, end))
+        return true;
+    return false;
+}
+
+/* TODO: probably should move to some other file and change name to
+   sleep_milliseconds */
+void sleep_milliseconds(int milliseconds)
+{
+#ifdef _WIN32
+    Sleep((DWORD)milliseconds);
+#else
+    struct timespec tv;
+    int             secs, nanosecs;
+    secs = milliseconds / 1000;
+    nanosecs = (milliseconds - (secs * 1000)) * 1000;
+    tv.tv_sec = (time_t) secs;
+    tv.tv_nsec = (long) nanosecs;
+    while (1)
+    {
+        int rval = nanosleep(&tv, &tv);
+        if (rval == 0)
+            /* Completed the entire sleep time; all done. */
+            return;
+        else if (errno == EINTR)
+            /* Interrupted by a signal. Try again. */
+            continue;
+        else
+            /* Some other error; bail out. */
+            return;
+    }
+    return;
+#endif
+}
+
+#ifndef HAVE_STRCPY_S
+void strcpy_s(char* dst, size_t dst_size, const char* src)
+{
+    size_t src_size = strlen(src) + 1;
+    if (src_size <= dst_size)
+        memcpy(dst, src, src_size);
+    else {
+        if (dst_size > 0) {
+            memcpy(dst, src, dst_size);
+            dst[dst_size-1] = 0;
+        }
+    }
+}
+#endif
+
+#ifndef HAVE_STRCAT_S
+void strcat_s(char *dst, size_t dst_size, const char* src)
+{
+    size_t dst_len = strlen(dst);
+    if (dst_len >= dst_size) {
+        if (dst_size > 0)
+            dst[dst_size-1] = 0;
+        return;
+    }
+    strcpy_s(dst+dst_len, dst_size - dst_len, src);
+}
+#endif
+
+static SplashColorMode gSplashColorMode = splashModeBGR8;
+
+static SplashColor splashColRed;
+static SplashColor splashColGreen;
+static SplashColor splashColBlue;
+static SplashColor splashColWhite;
+static SplashColor splashColBlack;
+
+#define SPLASH_COL_RED_PTR (SplashColorPtr)&(splashColRed[0])
+#define SPLASH_COL_GREEN_PTR (SplashColorPtr)&(splashColGreen[0])
+#define SPLASH_COL_BLUE_PTR (SplashColorPtr)&(splashColBlue[0])
+#define SPLASH_COL_WHITE_PTR (SplashColorPtr)&(splashColWhite[0])
+#define SPLASH_COL_BLACK_PTR (SplashColorPtr)&(splashColBlack[0])
+
+static SplashColorPtr  gBgColor = SPLASH_COL_WHITE_PTR;
+
+static void splashColorSet(SplashColorPtr col, Guchar red, Guchar green, Guchar blue, Guchar alpha)
+{
+    switch (gSplashColorMode)
+    {
+        case splashModeBGR8:
+            col[0] = blue;
+            col[1] = green;
+            col[2] = red;
+            break;
+        case splashModeRGB8:
+            col[0] = red;
+            col[1] = green;
+            col[2] = blue;
+            break;
+        default:
+            assert(0);
+            break;
+    }
+}
+
+void SplashColorsInit(void)
+{
+    splashColorSet(SPLASH_COL_RED_PTR, 0xff, 0, 0, 0);
+    splashColorSet(SPLASH_COL_GREEN_PTR, 0, 0xff, 0, 0);
+    splashColorSet(SPLASH_COL_BLUE_PTR, 0, 0, 0xff, 0);
+    splashColorSet(SPLASH_COL_BLACK_PTR, 0, 0, 0, 0);
+    splashColorSet(SPLASH_COL_WHITE_PTR, 0xff, 0xff, 0xff, 0);
+}
+
+PdfEnginePoppler::PdfEnginePoppler() : 
+   _fileName(0)
+   , _pageCount(INVALID_PAGE_NO) 
+   , _pdfDoc(NULL)
+   , _outputDev(NULL)
+{
+}
+
+PdfEnginePoppler::~PdfEnginePoppler()
+{
+    free(_fileName);
+    delete _outputDev;
+    delete _pdfDoc;
+}
+
+bool PdfEnginePoppler::load(const char *fileName)
+{
+    setFileName(fileName);
+    /* note: don't delete fileNameStr since PDFDoc takes ownership and deletes them itself */
+    GooString *fileNameStr = new GooString(fileName);
+    if (!fileNameStr) return false;
+
+    _pdfDoc = new PDFDoc(fileNameStr, NULL, NULL, (void*)NULL);
+    if (!_pdfDoc->isOk()) {
+        return false;
+    }
+    _pageCount = _pdfDoc->getNumPages();
+    return true;
+}
+
+SplashOutputDev * PdfEnginePoppler::outputDevice() {
+    if (!_outputDev) {
+        GBool bitmapTopDown = gTrue;
+        _outputDev = new SplashOutputDev(gSplashColorMode, 4, gFalse, gBgColor, bitmapTopDown);
+        if (_outputDev)
+            _outputDev->startDoc(_pdfDoc);
+    }
+    return _outputDev;
+}
+
+SplashBitmap *PdfEnginePoppler::renderBitmap(int pageNo, double zoomReal, int rotation)
+{
+    assert(outputDevice());
+    if (!outputDevice()) return NULL;
+
+    double hDPI = (double)PDF_FILE_DPI * zoomReal * 0.01;
+    double vDPI = (double)PDF_FILE_DPI * zoomReal * 0.01;
+    GBool  useMediaBox = gFalse;
+    GBool  crop        = gTrue;
+    GBool  doLinks     = gTrue;
+    _pdfDoc->displayPage(_outputDev, pageNo, hDPI, vDPI, rotation, useMediaBox, 
+        crop, doLinks, NULL, NULL);
+
+    SplashBitmap* bmp = _outputDev->takeBitmap();
+    return bmp;
+}
+
+struct FindFileState {
+    char path[MAX_FILENAME_SIZE];
+    char dirpath[MAX_FILENAME_SIZE]; /* current dir path */
+    char pattern[MAX_FILENAME_SIZE]; /* search pattern */
+    const char *bufptr;
+#ifdef _WIN32
+    WIN32_FIND_DATA fileinfo;
+    HANDLE dir;
+#else
+    DIR *dir;
+#endif
+};
+
+#ifdef _WIN32
+#include <sys/timeb.h>
+#include <direct.h>
+
+__inline char *getcwd(char *buffer, int maxlen)
+{
+    return _getcwd(buffer, maxlen);
+}
+
+int fnmatch(const char *pattern, const char *string, int flags)
+{
+    int prefix_len;
+    const char *star_pos = strchr(pattern, '*');
+    if (!star_pos)
+        return strcmp(pattern, string) != 0;
+
+    prefix_len = (int)(star_pos-pattern);
+    if (0 == prefix_len)
+        return 0;
+
+    if (0 == _strnicmp(pattern, string, prefix_len))
+        return 0;
+
+    return 1;
+}
+
+#else
+#include <fnmatch.h>
+#endif
+
+#ifdef _WIN32
+/* on windows to query dirs we need foo\* to get files in this directory.
+    foo\ always fails and foo will return just info about foo directory,
+    not files in this directory */
+static void win_correct_path_for_FindFirstFile(char *path, int path_max_len)
+{
+    int path_len = strlen(path);
+    if (path_len >= path_max_len-4)
+        return;
+    if (DIR_SEP_CHAR != path[path_len])
+        path[path_len++] = DIR_SEP_CHAR;
+    path[path_len++] = '*';
+    path[path_len] = 0;
+}
+#endif
+
+FindFileState *find_file_open(const char *path, const char *pattern)
+{
+    FindFileState *s;
+
+    s = (FindFileState*)malloc(sizeof(FindFileState));
+    if (!s)
+        return NULL;
+    strcpy_s(s->path, sizeof(s->path), path);
+    strcpy_s(s->dirpath, sizeof(s->path), path);
+#ifdef _WIN32
+    win_correct_path_for_FindFirstFile(s->path, sizeof(s->path));
+#endif
+    strcpy_s(s->pattern, sizeof(s->pattern), pattern);
+    s->bufptr = s->path;
+#ifdef _WIN32
+    s->dir = INVALID_HANDLE_VALUE;
+#else
+    s->dir = NULL;
+#endif
+    return s;
+}
+
+#if 0 /* re-enable if we #define USE_OWN_GET_AUTH_DATA */
+void *StandardSecurityHandler::getAuthData()
+{
+    return NULL;
+}
+#endif
+
+char *makepath(char *buf, int buf_size, const char *path,
+               const char *filename)
+{
+    strcpy_s(buf, buf_size, path);
+    int len = strlen(path);
+    if (len > 0 && path[len - 1] != DIR_SEP_CHAR && len + 1 < buf_size) {
+        buf[len++] = DIR_SEP_CHAR;
+        buf[len] = '\0';
+    }
+    strcat_s(buf, buf_size, filename);
+    return buf;
+}
+
+#ifdef _WIN32
+static int skip_matching_file(const char *filename)
+{
+    if (0 == strcmp(".", filename))
+        return 1;
+    if (0 == strcmp("..", filename))
+        return 1;
+    return 0;
+}
+#endif
+
+int find_file_next(FindFileState *s, char *filename, int filename_size_max)
+{
+#ifdef _WIN32
+    int    fFound;
+    if (INVALID_HANDLE_VALUE == s->dir) {
+        s->dir = FindFirstFile(s->path, &(s->fileinfo));
+        if (INVALID_HANDLE_VALUE == s->dir)
+            return -1;
+        goto CheckFile;
+    }
+
+    while (1) {
+        fFound = FindNextFile(s->dir, &(s->fileinfo));
+        if (!fFound)
+            return -1;
+CheckFile:
+        if (skip_matching_file(s->fileinfo.cFileName))
+            continue;
+        if (0 == fnmatch(s->pattern, s->fileinfo.cFileName, 0) ) {
+            makepath(filename, filename_size_max, s->dirpath, s->fileinfo.cFileName);
+            return 0;
+        }
+    }
+#else
+    struct dirent *dirent;
+    const char *p;
+    char *q;
+
+    if (s->dir == NULL)
+        goto redo;
+
+    for (;;) {
+        dirent = readdir(s->dir);
+        if (dirent == NULL) {
+        redo:
+            if (s->dir) {
+                closedir(s->dir);
+                s->dir = NULL;
+            }
+            p = s->bufptr;
+            if (*p == '\0')
+                return -1;
+            /* CG: get_str(&p, s->dirpath, sizeof(s->dirpath), ":") */
+            q = s->dirpath;
+            while (*p != ':' && *p != '\0') {
+                if ((q - s->dirpath) < (int)sizeof(s->dirpath) - 1)
+                    *q++ = *p;
+                p++;
+            }
+            *q = '\0';
+            if (*p == ':')
+                p++;
+            s->bufptr = p;
+            s->dir = opendir(s->dirpath);
+            if (!s->dir)
+                goto redo;
+        } else {
+            if (fnmatch(s->pattern, dirent->d_name, 0) == 0) {
+                makepath(filename, filename_size_max,
+                         s->dirpath, dirent->d_name);
+                return 0;
+            }
+        }
+    }
+#endif
+}
+
+void find_file_close(FindFileState *s)
+{
+#ifdef _WIN32
+    if (INVALID_HANDLE_VALUE != s->dir)
+       FindClose(s->dir);
+#else
+    if (s->dir)
+        closedir(s->dir);
+#endif
+    free(s);
+}
+
+int StrList_Len(StrList **root)
+{
+    int         len = 0;
+    StrList *   cur;
+    assert(root);
+    if (!root)
+        return 0;
+    cur = *root;
+    while (cur) {
+        ++len;
+        cur = cur->next;
+    }
+    return len;
+}
+
+int StrList_InsertAndOwn(StrList **root, char *txt)
+{
+    StrList *   el;
+    assert(root && txt);
+    if (!root || !txt)
+        return false;
+
+    el = (StrList*)malloc(sizeof(StrList));
+    if (!el)
+        return false;
+    el->str = txt;
+    el->next = *root;
+    *root = el;
+    return true;
+}
+
+int StrList_Insert(StrList **root, char *txt)
+{
+    char *txtDup;
+
+    assert(root && txt);
+    if (!root || !txt)
+        return false;
+    txtDup = str_dup(txt);
+    if (!txtDup)
+        return false;
+
+    if (!StrList_InsertAndOwn(root, txtDup)) {
+        free((void*)txtDup);
+        return false;
+    }
+    return true;
+}
+
+StrList* StrList_RemoveHead(StrList **root)
+{
+    StrList *tmp;
+    assert(root);
+    if (!root)
+        return NULL;
+
+    if (!*root)
+        return NULL;
+    tmp = *root;
+    *root = tmp->next;
+    tmp->next = NULL;
+    return tmp;
+}
+
+void StrList_FreeElement(StrList *el)
+{
+    if (!el)
+        return;
+    free((void*)el->str);
+    free((void*)el);
+}
+
+void StrList_Destroy(StrList **root)
+{
+    StrList *   cur;
+    StrList *   next;
+
+    if (!root)
+        return;
+    cur = *root;
+    while (cur) {
+        next = cur->next;
+        StrList_FreeElement(cur);
+        cur = next;
+    }
+    *root = NULL;
+}
+
+#ifndef _WIN32
+void OutputDebugString(const char *txt)
+{
+    /* do nothing */
+}
+#define _snprintf snprintf
+#define _vsnprintf vsnprintf
+#endif
+
+void my_error(void *, ErrorCategory, Goffset pos, char *msg) {
+#if 0
+    char        buf[4096], *p = buf;
+
+    // NB: this can be called before the globalParams object is created
+    if (globalParams && globalParams->getErrQuiet()) {
+        return;
+    }
+
+    if (pos >= 0) {
+      p += _snprintf(p, sizeof(buf)-1, "Error (%lld): ", (long long)pos);
+        *p   = '\0';
+        OutputDebugString(p);
+    } else {
+        OutputDebugString("Error: ");
+    }
+
+    p = buf;
+    p += _vsnprintf(p, sizeof(buf) - 1, msg, args);
+    while ( p > buf  &&  isspace(p[-1]) )
+            *--p = '\0';
+    *p++ = '\r';
+    *p++ = '\n';
+    *p   = '\0';
+    OutputDebugString(buf);
+
+    if (pos >= 0) {
+        p += _snprintf(p, sizeof(buf)-1, "Error (%lld): ", (long long)pos);
+        *p   = '\0';
+        OutputDebugString(buf);
+        if (gErrFile)
+            fprintf(gErrFile, buf);
+    } else {
+        OutputDebugString("Error: ");
+        if (gErrFile)
+            fprintf(gErrFile, "Error: ");
+    }
+#endif
+#if 0
+    p = buf;
+    va_start(args, msg);
+    p += _vsnprintf(p, sizeof(buf) - 3, msg, args);
+    while ( p > buf  &&  isspace(p[-1]) )
+            *--p = '\0';
+    *p++ = '\r';
+    *p++ = '\n';
+    *p   = '\0';
+    OutputDebugString(buf);
+    if (gErrFile)
+        fprintf(gErrFile, buf);
+    va_end(args);
+#endif
+}
+
+void LogInfo(const char *fmt, ...)
+{
+    va_list args;
+    char        buf[4096], *p = buf;
+
+    p = buf;
+    va_start(args, fmt);
+    p += _vsnprintf(p, sizeof(buf) - 1, fmt, args);
+    *p   = '\0';
+    fprintf(gOutFile, "%s", buf);
+    va_end(args);
+    fflush(gOutFile);
+}
+
+static void PrintUsageAndExit(int argc, char **argv)
+{
+    printf("Usage: pdftest [-preview|-slowpreview] [-loadonly] [-timings] [-text] [-resolution NxM] [-recursive] [-page N] [-out out.txt] pdf-files-to-process\n");
+    for (int i=0; i < argc; i++) {
+        printf("i=%d, '%s'\n", i, argv[i]);
+    }
+    exit(0);
+}
+
+static bool ShowPreview(void)
+{
+    if (gfPreview || gfSlowPreview)
+        return true;
+    return false;
+}
+
+static void RenderPdfAsText(const char *fileName)
+{
+    GooString *         fileNameStr = NULL;
+    PDFDoc *            pdfDoc = NULL;
+    GooString *         txt = NULL;
+    int                 pageCount;
+    double              timeInMs;
+
+    assert(fileName);
+    if (!fileName)
+        return;
+
+    LogInfo("started: %s\n", fileName);
+
+    TextOutputDev * textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse);
+    if (!textOut->isOk()) {
+        delete textOut;
+        return;
+    }
+
+    GooTimer msTimer;
+    /* note: don't delete fileNameStr since PDFDoc takes ownership and deletes them itself */
+    fileNameStr = new GooString(fileName);
+    if (!fileNameStr)
+        goto Exit;
+
+    pdfDoc = new PDFDoc(fileNameStr, NULL, NULL, NULL);
+    if (!pdfDoc->isOk()) {
+        error(errIO, -1, "RenderPdfFile(): failed to open PDF file {0:s}\n", fileName);
+        goto Exit;
+    }
+
+    msTimer.stop();
+    timeInMs = msTimer.getElapsed();
+    LogInfo("load: %.2f ms\n", timeInMs);
+
+    pageCount = pdfDoc->getNumPages();
+    LogInfo("page count: %d\n", pageCount);
+
+    for (int curPage = 1; curPage <= pageCount; curPage++) {
+        if ((gPageNo != PAGE_NO_NOT_GIVEN) && (gPageNo != curPage))
+            continue;
+
+        msTimer.start();
+        int rotate = 0;
+        GBool useMediaBox = gFalse;
+        GBool crop = gTrue;
+        GBool doLinks = gFalse;
+        pdfDoc->displayPage(textOut, curPage, 72, 72, rotate, useMediaBox, crop, doLinks);
+        txt = textOut->getText(0.0, 0.0, 10000.0, 10000.0);
+        msTimer.stop();
+        timeInMs = msTimer.getElapsed();
+        if (gfTimings)
+            LogInfo("page %d: %.2f ms\n", curPage, timeInMs);
+        printf("%s\n", txt->getCString());
+        delete txt;
+        txt = NULL;
+    }
+
+Exit:
+    LogInfo("finished: %s\n", fileName);
+    delete textOut;
+    delete pdfDoc;
+}
+
+#ifdef _MSC_VER
+#define POPPLER_TMP_NAME "c:\\poppler_tmp.pdf"
+#else
+#define POPPLER_TMP_NAME "/tmp/poppler_tmp.pdf"
+#endif
+
+static void RenderPdf(const char *fileName)
+{
+    const char *        fileNameSplash = NULL;
+    PdfEnginePoppler *  engineSplash = NULL;
+    int                 pageCount;
+    double              timeInMs;
+
+#ifdef COPY_FILE
+    // TODO: fails if file already exists and has read-only attribute
+    CopyFile(fileName, POPPLER_TMP_NAME, false);
+    fileNameSplash = POPPLER_TMP_NAME;
+#else
+    fileNameSplash = fileName;
+#endif
+    LogInfo("started: %s\n", fileName);
+
+    engineSplash = new PdfEnginePoppler();
+
+    GooTimer msTimer;
+    if (!engineSplash->load(fileNameSplash)) {
+        LogInfo("failed to load splash\n");
+        goto Error;
+    }
+    msTimer.stop();
+    timeInMs = msTimer.getElapsed();
+    LogInfo("load splash: %.2f ms\n", timeInMs);
+    pageCount = engineSplash->pageCount();
+
+    LogInfo("page count: %d\n", pageCount);
+    if (gfLoadOnly)
+        goto Error;
+
+    for (int curPage = 1; curPage <= pageCount; curPage++) {
+        if ((gPageNo != PAGE_NO_NOT_GIVEN) && (gPageNo != curPage))
+            continue;
+
+        SplashBitmap *bmpSplash = NULL;
+
+        GooTimer msTimer;
+        bmpSplash = engineSplash->renderBitmap(curPage, 100.0, 0);
+        msTimer.stop();
+        double timeInMs = msTimer.getElapsed();
+        if (gfTimings) {
+            if (!bmpSplash)
+                LogInfo("page splash %d: failed to render\n", curPage);
+            else
+                LogInfo("page splash %d (%dx%d): %.2f ms\n", curPage, bmpSplash->getWidth(), bmpSplash->getHeight(), timeInMs);
+        }
+
+        if (ShowPreview()) {
+            PreviewBitmapSplash(bmpSplash);
+            if (gfSlowPreview)
+                sleep_milliseconds(SLOW_PREVIEW_TIME);
+        }
+        delete bmpSplash;
+    }
+Error:
+    delete engineSplash;
+    LogInfo("finished: %s\n", fileName);
+}
+
+static void RenderFile(const char *fileName)
+{
+    if (gfTextOnly) {
+        RenderPdfAsText(fileName);
+        return;
+    }
+
+    RenderPdf(fileName);
+}
+
+static bool ParseInteger(const char *start, const char *end, int *intOut)
+{
+    char            numBuf[16];
+    int             digitsCount;
+    const char *    tmp;
+
+    assert(start && end && intOut);
+    assert(end >= start);
+    if (!start || !end || !intOut || (start > end))
+        return false;
+
+    digitsCount = 0;
+    tmp = start;
+    while (tmp <= end) {
+        if (isspace(*tmp)) {
+            /* do nothing, we allow whitespace */
+        } else if (!isdigit(*tmp))
+            return false;
+        numBuf[digitsCount] = *tmp;
+        ++digitsCount;
+        if (digitsCount == dimof(numBuf)-3) /* -3 to be safe */
+            return false;
+        ++tmp;
+    }
+    if (0 == digitsCount)
+        return false;
+    numBuf[digitsCount] = 0;
+    *intOut = atoi(numBuf);
+    return true;
+}
+
+/* Given 'resolutionString' in format NxM (e.g. "100x200"), parse the string and put N
+   into 'resolutionXOut' and M into 'resolutionYOut'.
+   Return false if there was an error (e.g. string is not in the right format */
+static bool ParseResolutionString(const char *resolutionString, int *resolutionXOut, int *resolutionYOut)
+{
+    const char *    posOfX;
+
+    assert(resolutionString);
+    assert(resolutionXOut);
+    assert(resolutionYOut);
+    if (!resolutionString || !resolutionXOut || !resolutionYOut)
+        return false;
+    *resolutionXOut = 0;
+    *resolutionYOut = 0;
+    posOfX = strchr(resolutionString, 'X');
+    if (!posOfX)
+        posOfX = strchr(resolutionString, 'x');
+    if (!posOfX)
+        return false;
+    if (posOfX == resolutionString)
+        return false;
+    if (!ParseInteger(resolutionString, posOfX-1, resolutionXOut))
+        return false;
+    if (!ParseInteger(posOfX+1, resolutionString+strlen(resolutionString)-1, resolutionYOut))
+        return false;
+    return true;
+}
+
+static void ParseCommandLine(int argc, char **argv)
+{
+    char *      arg;
+
+    if (argc < 2)
+        PrintUsageAndExit(argc, argv);
+
+    for (int i=1; i < argc; i++) {
+        arg = argv[i];
+        assert(arg);
+        if ('-' == arg[0]) {
+            if (str_ieq(arg, TIMINGS_ARG)) {
+                gfTimings = true;
+            } else if (str_ieq(arg, RESOLUTION_ARG)) {
+                ++i;
+                if (i == argc)
+                    PrintUsageAndExit(argc, argv); /* expect a file name after that */
+                if (!ParseResolutionString(argv[i], &gResolutionX, &gResolutionY))
+                    PrintUsageAndExit(argc, argv);
+                gfForceResolution = true;
+            } else if (str_ieq(arg, RECURSIVE_ARG)) {
+                gfRecursive = true;
+            } else if (str_ieq(arg, OUT_ARG)) {
+                /* expect a file name after that */
+                ++i;
+                if (i == argc)
+                    PrintUsageAndExit(argc, argv);
+                gOutFileName = str_dup(argv[i]);
+            } else if (str_ieq(arg, PREVIEW_ARG)) {
+                gfPreview = true;
+            } else if (str_ieq(arg, TEXT_ARG)) {
+                gfTextOnly = true;
+            } else if (str_ieq(arg, SLOW_PREVIEW_ARG)) {
+                gfSlowPreview = true;
+            } else if (str_ieq(arg, LOAD_ONLY_ARG)) {
+                gfLoadOnly = true;
+            } else if (str_ieq(arg, PAGE_ARG)) {
+                /* expect an integer after that */
+                ++i;
+                if (i == argc)
+                    PrintUsageAndExit(argc, argv);
+                gPageNo = atoi(argv[i]);
+                if (gPageNo < 1)
+                    PrintUsageAndExit(argc, argv);
+            } else {
+                /* unknown option */
+                PrintUsageAndExit(argc, argv);
+            }
+        } else {
+            /* we assume that this is not an option hence it must be
+               a name of PDF/directory/file with PDF names */
+            StrList_Insert(&gArgsListRoot, arg);
+        }
+    }
+}
+
+#if 0
+void RenderFileList(char *pdfFileList)
+{
+    char *data = NULL;
+    char *dataNormalized = NULL;
+    char *pdfFileName;
+    uint64_t fileSize;
+
+    assert(pdfFileList);
+    if (!pdfFileList)
+        return;
+    data = file_read_all(pdfFileList, &fileSize);
+    if (!data) {
+        error(-1, "couldn't load file '%s'", pdfFileList);
+        return;
+    }
+    dataNormalized = str_normalize_newline(data, UNIX_NEWLINE);
+    if (!dataNormalized) {
+        error(-1, "couldn't normalize data of file '%s'", pdfFileList);
+        goto Exit;
+    }
+    for (;;) {
+        pdfFileName = str_split_iter(&dataNormalized, UNIX_NEWLINE_C);
+        if (!pdfFileName)
+            break;
+        str_strip_ws_both(pdfFileName);
+        if (str_empty(pdfFileName)) {
+            free((void*)pdfFileName);
+            continue;
+        }
+        RenderFile(pdfFileName);
+        free((void*)pdfFileName);
+    }
+Exit:
+    free((void*)dataNormalized);
+    free((void*)data);
+}
+#endif
+
+#ifdef _WIN32
+#include <sys/types.h>
+#include <sys/stat.h>
+
+bool IsDirectoryName(char *path)
+{
+    struct _stat    buf;
+    int             result;
+
+    result = _stat(path, &buf );
+    if (0 != result)
+        return false;
+
+    if (buf.st_mode & _S_IFDIR)
+        return true;
+
+    return false;
+}
+
+bool IsFileName(char *path)
+{
+    struct _stat    buf;
+    int             result;
+
+    result = _stat(path, &buf );
+    if (0 != result)
+        return false;
+
+    if (buf.st_mode & _S_IFREG)
+        return true;
+
+    return false;
+}
+#else
+bool IsDirectoryName(char *path)
+{
+    /* TODO: implement me */
+    return false;
+}
+
+bool IsFileName(char *path)
+{
+    /* TODO: implement me */
+    return true;
+}
+#endif
+
+bool IsPdfFileName(char *path)
+{
+    if (str_endswith(path, ".pdf"))
+        return true;
+    return false;
+}
+
+static void RenderDirectory(char *path)
+{
+    FindFileState * ffs;
+    char            filename[MAX_FILENAME_SIZE];
+    StrList *       dirList = NULL;
+    StrList *       el;
+
+    StrList_Insert(&dirList, path);
+
+    while (0 != StrList_Len(&dirList)) {
+        el = StrList_RemoveHead(&dirList);
+        ffs = find_file_open(el->str, "*");
+        while (!find_file_next(ffs, filename, sizeof(filename))) {
+            if (IsDirectoryName(filename)) {
+                if (gfRecursive) {
+                    StrList_Insert(&dirList, filename);
+                }
+            } else if (IsFileName(filename)) {
+                if (IsPdfFileName(filename)) {
+                    RenderFile(filename);
+                }
+            }
+        }
+        find_file_close(ffs);
+        StrList_FreeElement(el);
+    }
+    StrList_Destroy(&dirList);
+}
+
+/* Render 'cmdLineArg', which can be:
+   - directory name
+   - name of PDF file
+   - name of text file with names of PDF files
+*/
+static void RenderCmdLineArg(char *cmdLineArg)
+{
+    assert(cmdLineArg);
+    if (!cmdLineArg)
+        return;
+    if (IsDirectoryName(cmdLineArg)) {
+        RenderDirectory(cmdLineArg);
+    } else if (IsFileName(cmdLineArg)) {
+        if (IsPdfFileName(cmdLineArg))
+            RenderFile(cmdLineArg);
+#if 0
+        else
+            RenderFileList(cmdLineArg);
+#endif
+    } else {
+        error(errCommandLine, -1, "unexpected argument '{0:s}'", cmdLineArg);
+    }
+}
+
+int main(int argc, char **argv)
+{
+    setErrorCallback(my_error, NULL);
+    ParseCommandLine(argc, argv);
+    if (0 == StrList_Len(&gArgsListRoot))
+        PrintUsageAndExit(argc, argv);
+    assert(gArgsListRoot);
+
+    SplashColorsInit();
+    globalParams = new GlobalParams();
+    if (!globalParams)
+        return 1;
+    globalParams->setErrQuiet(gFalse);
+
+    FILE * outFile = NULL;
+    if (gOutFileName) {
+        outFile = fopen(gOutFileName, "wb");
+        if (!outFile) {
+            printf("failed to open -out file %s\n", gOutFileName);
+            return 1;
+        }
+        gOutFile = outFile;
+    }
+    else
+        gOutFile = stdout;
+
+    if (gOutFileName)
+        gErrFile = outFile;
+    else
+        gErrFile = stderr;
+
+    PreviewBitmapInit();
+
+    StrList * curr = gArgsListRoot;
+    while (curr) {
+        RenderCmdLineArg(curr->str);
+        curr = curr->next;
+    }
+    if (outFile)
+        fclose(outFile);
+    PreviewBitmapDestroy();
+    StrList_Destroy(&gArgsListRoot);
+    delete globalParams;
+    free(gOutFileName);
+    return 0;
+}
+